Compare commits
91
Commits
main
...
51d408d524
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51d408d524 | ||
|
|
846f441e3a | ||
|
|
e02b0c633a | ||
|
|
b5a7a811b3 | ||
|
|
bb9d0a9f18 | ||
|
|
c30143609a | ||
|
|
b3e38ba7e4 | ||
|
|
16a1f030d4 | ||
|
|
4141e7d574 | ||
|
|
fb0ff9b663 | ||
|
|
70ac7380e9 | ||
|
|
73c9a40abd | ||
|
|
b8e2bba140 | ||
|
|
1a6df012cd | ||
|
|
5de4937637 | ||
|
|
2594a81034 | ||
|
|
acfbbeee8b | ||
|
|
54425a50c7 | ||
|
|
2a84b61e3d | ||
|
|
e4347cc3ea | ||
|
|
7cbe2d2b9a | ||
|
|
f6a0bce9f0 | ||
|
|
09d05e3a34 | ||
|
|
06f3884c30 | ||
|
|
be41a82d55 | ||
|
|
983746c797 | ||
|
|
3175e04e6e | ||
|
|
a621e34d51 | ||
|
|
a1e684f6d4 | ||
|
|
5073c2b3ea | ||
|
|
9bdb7f892e | ||
|
|
72ae6d6994 | ||
|
|
a64a96f9a4 | ||
|
|
87d9b7ea34 | ||
|
|
49ccdec3dd | ||
|
|
ab6e99c958 | ||
|
|
bcd26d3021 | ||
|
|
8aa7a89c7e | ||
|
|
98b35a98e3 | ||
|
|
34df303b56 | ||
|
|
3d57260f3f | ||
|
|
f1d86b6e34 | ||
|
|
0f40de43a1 | ||
|
|
d6113a5417 | ||
|
|
c81a57dacb | ||
|
|
b2e6ade484 | ||
|
|
994a35a942 | ||
|
|
4d9509c167 | ||
|
|
d3c63dab5c | ||
|
|
78d767bf21 | ||
|
|
2a7db9e48c | ||
|
|
c06d7247bb | ||
|
|
b0e6b406ac | ||
|
|
314100afbc | ||
|
|
a56b4cbb70 | ||
|
|
ca7cf221c8 | ||
|
|
2ccee5f1cf | ||
|
|
8ac59dfaa1 | ||
|
|
74d7376039 | ||
|
|
a4ec5656d2 | ||
|
|
b43042c88d | ||
|
|
a77eade06f | ||
|
|
89d4bae8db | ||
|
|
02095524b1 | ||
|
|
da9dae734c | ||
|
|
a2d5dd9ce8 | ||
|
|
7c5bb41e09 | ||
|
|
459b6188a9 | ||
|
|
21f35b30fa | ||
|
|
ab63c8ed90 | ||
|
|
f7f71c5f52 | ||
|
|
b67edc5cee | ||
|
|
1ceda90207 | ||
|
|
9b9b25ce0f | ||
|
|
118c153280 | ||
|
|
f1b4225c92 | ||
|
|
53ae30f1ca | ||
|
|
147f26e063 | ||
|
|
463177d3be | ||
|
|
1df3e3bcb4 | ||
|
|
3cf11f922e | ||
|
|
69cea030dc | ||
|
|
0892c084ee | ||
|
|
ad473f245c | ||
|
|
0182b8ed5c | ||
|
|
777b6b42d1 | ||
|
|
5dadf6d6ee | ||
|
|
67c8a137dd | ||
|
|
e2e0ebc21f | ||
|
|
51d8510b79 | ||
|
|
50b07074fb |
No files matched your search
@@ -2,3 +2,12 @@
|
||||
!.gitignore
|
||||
|
||||
build/
|
||||
|
||||
*.sir
|
||||
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
*.old
|
||||
*.exe
|
||||
*.out
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
Root = { SOI ~ ContainerMembers ~ EOI }
|
||||
ContainerMembers = { ExternalHeaderList ~ (ContainerDeclaration ~ (COMMA | SEMICOLON)*)* }
|
||||
|
||||
HeaderItem = { (AT | SHARP) ~ IDENTIFIER ~ (LPAREN ~ ExprList? ~ RPAREN)? }
|
||||
HeaderList = { HeaderItem* }
|
||||
DeclHeader = { HeaderList? ~ KEYWORD_pub? }
|
||||
ExternalHeaderItem = { (AT | SHARP) ~ BANG ~ IDENTIFIER ~ (LPAREN ~ ExprList? ~ RPAREN)? }
|
||||
ExternalHeaderList = { ExternalHeaderItem* }
|
||||
|
||||
BindingIdent = { IDENTIFIER | UNDERSCORE }
|
||||
|
||||
ContainerDeclaration = {
|
||||
FnDecl
|
||||
| VarDecl
|
||||
| ConstDecl
|
||||
| TypeDecl
|
||||
| MemberDecl
|
||||
| ComptimeDecl
|
||||
| HeaderList ~ Block
|
||||
}
|
||||
|
||||
ComptimeDecl = { KEYWORD_comptime ~ (Block | Expr) }
|
||||
|
||||
FnDecl = {
|
||||
DeclHeader ~ KEYWORD_comptime? ~ KEYWORD_fn ~ IDENTIFIER ~ LPAREN ~ ParamDeclList ~ RPAREN ~ (COLON ~ TypeExpr)? ~ (SEMICOLON | Block)
|
||||
}
|
||||
|
||||
ParamDeclList = { (ParamDecl ~ COMMA)* ~ (ParamDecl | DOT3)? }
|
||||
ParamDecl = { HeaderList? ~ BindingIdent ~ COLON ~ TypeExpr }
|
||||
|
||||
VarDecl = { DeclHeader ~ KEYWORD_var ~ VarBody }
|
||||
ConstDecl = { DeclHeader ~ KEYWORD_const ~ VarBody }
|
||||
VarBody = { Pattern ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ Expr)? }
|
||||
|
||||
TypeDecl = { DeclHeader ~ KEYWORD_type ~ Pattern ~ EQUAL ~ TypeExpr }
|
||||
|
||||
MemberDecl = { DeclHeader ~ IDENTIFIER ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ Expr)? }
|
||||
|
||||
Pattern = { PatternAtom ~ (COMMA ~ PatternAtom)* }
|
||||
PatternAtom = {
|
||||
UNDERSCORE
|
||||
| AnonymousAggregatePattern
|
||||
| DOT ~ IDENTIFIER ~ (LPAREN ~ Pattern? ~ RPAREN)?
|
||||
| LPAREN ~ Pattern? ~ RPAREN
|
||||
| PatternLiteral
|
||||
| IDENTIFIER
|
||||
}
|
||||
|
||||
AnonymousAggregatePattern = { DOT ~ LBRACE ~ PatternFieldList? ~ RBRACE }
|
||||
PatternFieldList = { PatternField ~ ((COMMA | SEMICOLON) ~ PatternField)* ~ (COMMA | SEMICOLON)? }
|
||||
PatternField = { DOT ~ IDENTIFIER ~ (EQUAL ~ PatternAtom)? }
|
||||
|
||||
PatternLiteral = {
|
||||
FLOAT_LITERAL
|
||||
| INTEGER_LITERAL
|
||||
| CHAR_LITERAL
|
||||
| STRING_LITERAL
|
||||
| KEYWORD_true
|
||||
| KEYWORD_false
|
||||
| KEYWORD_null
|
||||
}
|
||||
|
||||
Block = { LBRACE ~ Statement* ~ Expr? ~ RBRACE }
|
||||
Statement = {
|
||||
RetStatement
|
||||
| BreakStatement
|
||||
| ContinueStatement
|
||||
| DeferStatement
|
||||
| DeclStatement
|
||||
| ExprStatement
|
||||
| ComptimeStatement
|
||||
}
|
||||
|
||||
DeclStatement = { (VarDecl | ConstDecl | TypeDecl) ~ (COMMA | SEMICOLON)? }
|
||||
ExprStatement = { Expr ~ (COMMA | SEMICOLON)? }
|
||||
ComptimeStatement = { ComptimeDecl ~ (COMMA | SEMICOLON)? }
|
||||
|
||||
RetStatement = { KEYWORD_ret ~ Expr? ~ (COMMA | SEMICOLON)? }
|
||||
BreakStatement = { KEYWORD_break ~ Label? ~ Expr? ~ (COMMA | SEMICOLON)? }
|
||||
ContinueStatement = { KEYWORD_continue ~ Label? ~ (COMMA | SEMICOLON)? }
|
||||
DeferStatement = { KEYWORD_defer ~ (Block | Statement) ~ (COMMA | SEMICOLON)? }
|
||||
|
||||
Label = { DOLLAR ~ IDENTIFIER }
|
||||
|
||||
Expr = { AssignExpr }
|
||||
|
||||
AssignExpr = { BoolOrExpr ~ (AssignOp ~ AssignExpr)? }
|
||||
BoolOrExpr = { BoolAndExpr ~ (BOOL_OR ~ BoolAndExpr)* }
|
||||
BoolAndExpr = { BitOrExpr ~ (BOOL_AND ~ BitOrExpr)* }
|
||||
BitOrExpr = { BitXorExpr ~ (PIPE ~ BitXorExpr)* }
|
||||
BitXorExpr = { BitAndExpr ~ (CARET ~ BitAndExpr)* }
|
||||
BitAndExpr = { CmpEqExpr ~ (AMPERSAND ~ CmpEqExpr)* }
|
||||
CmpEqExpr = { CmpExpr ~ ((EQ_EQ | BANG_EQUAL) ~ CmpExpr)* }
|
||||
CmpExpr = { ArrowExpr ~ ((L_ARROW_EQ | L_ARROW | R_ARROW_EQ | R_ARROW) ~ ArrowExpr)* }
|
||||
|
||||
ArrowExpr = { RangeExpr ~ ((THIN_R_ARROW | THIN_L_ARROW | FAT_D_ARROW | THIN_D_ARROW) ~ RangeExpr)* }
|
||||
|
||||
RangeExpr = { ShiftExpr ~ (DOT2 ~ ShiftExpr?)? }
|
||||
ShiftExpr = { AddExpr ~ ((L_ARROW2_EQ | L_ARROW2 | R_ARROW2_EQ | R_ARROW2) ~ AddExpr)* }
|
||||
AddExpr = { MulExpr ~ ((PLUS | MINUS) ~ MulExpr)* }
|
||||
MulExpr = { CastExpr ~ ((ASTERISK | SLASH | PERCENT) ~ CastExpr)* }
|
||||
CastExpr = { PrefixExpr ~ (KEYWORD_as ~ TypeExpr)* }
|
||||
PrefixExpr = { PrefixOp* ~ PostfixExpr }
|
||||
PrefixOp = { MINUS | BANG | TILDE | AMPERSAND }
|
||||
|
||||
PostfixExpr = {
|
||||
PrimaryExpr ~ (LPAREN ~ ExprList ~ RPAREN | DOT ~ IDENTIFIER | DOT ~ ASTERISK | LBRACKET ~ Expr ~ RBRACKET | LBRACKET ~ Expr ~ DOT2 ~ Expr? ~ RBRACKET)*
|
||||
}
|
||||
|
||||
ExprList = { (Expr ~ COMMA)* ~ Expr? }
|
||||
|
||||
// 注:类型即表达式**仅限 comptime 上下文**(`comptime { ... }`、`comptime expr`、
|
||||
// `comptime fn` 的函数体)内。在这些上下文中,PrimaryExpr 额外接受
|
||||
// `AggregateTypeExpr`(如 `struct { ... }`)以及 `type` 元类型;其它上下文
|
||||
// 中类型只允许出现在 TypeExpr 位置。
|
||||
PrimaryExpr = {
|
||||
FLOAT_LITERAL
|
||||
| INTEGER_LITERAL
|
||||
| CHAR_LITERAL
|
||||
| STRING_LITERAL
|
||||
| KEYWORD_true
|
||||
| KEYWORD_false
|
||||
| KEYWORD_null
|
||||
| TupleLiteral
|
||||
| AggregateLiteral
|
||||
| AnonymousAggregateLiteral
|
||||
| ArrayLiteral
|
||||
| AnonymousArrayLiteral
|
||||
| VariantConstruct
|
||||
| ComptimeCallExpr
|
||||
| IDENTIFIER
|
||||
| Block
|
||||
| IfExpr
|
||||
| IfVarExpr
|
||||
| WhileExpr
|
||||
| LoopExpr
|
||||
| ForExpr
|
||||
| MatchExpr
|
||||
| FnTypeExpr
|
||||
}
|
||||
|
||||
TupleLiteral = { ParenExpr }
|
||||
|
||||
AggregateLiteral = { IDENTIFIER ~ LBRACE ~ StructInitList? ~ RBRACE }
|
||||
AnonymousAggregateLiteral = { DOT ~ LBRACE ~ StructInitList? ~ RBRACE }
|
||||
|
||||
StructInitList = { StructInit ~ ((COMMA | SEMICOLON) ~ StructInit)* ~ (COMMA | SEMICOLON)? }
|
||||
StructInit = { DOT ~ IDENTIFIER ~ (EQUAL ~ Expr)? }
|
||||
|
||||
VariantConstruct = { DOT ~ IDENTIFIER ~ (LPAREN ~ ExprList? ~ RPAREN)? }
|
||||
|
||||
ArrayLiteral = {
|
||||
LBRACKET ~ RBRACKET ~ TypeExpr ~ LBRACKET ~ ExprList? ~ RBRACKET
|
||||
| LBRACKET ~ (INTEGER_LITERAL | UNDERSCORE) ~ RBRACKET ~ TypeExpr ~ LBRACKET ~ ExprList? ~ RBRACKET
|
||||
}
|
||||
|
||||
AnonymousArrayLiteral = {
|
||||
DOT ~ LBRACKET ~ ExprList? ~ RBRACKET
|
||||
}
|
||||
|
||||
ExprNoStruct = { AssignExprNoStruct }
|
||||
|
||||
AssignExprNoStruct = { BoolOrExprNoStruct ~ (AssignOp ~ AssignExprNoStruct)? }
|
||||
BoolOrExprNoStruct = { BoolAndExprNoStruct ~ (BOOL_OR ~ BoolAndExprNoStruct)* }
|
||||
BoolAndExprNoStruct = { BitOrExprNoStruct ~ (BOOL_AND ~ BitOrExprNoStruct)* }
|
||||
BitOrExprNoStruct = { BitXorExprNoStruct ~ (PIPE ~ BitXorExprNoStruct)* }
|
||||
BitXorExprNoStruct = { BitAndExprNoStruct ~ (CARET ~ BitAndExprNoStruct)* }
|
||||
BitAndExprNoStruct = { CmpEqExprNoStruct ~ (AMPERSAND ~ CmpEqExprNoStruct)* }
|
||||
CmpEqExprNoStruct = { CmpExprNoStruct ~ ((EQ_EQ | BANG_EQUAL) ~ CmpExprNoStruct)* }
|
||||
CmpExprNoStruct = { ArrowExprNoStruct ~ ((L_ARROW_EQ | L_ARROW | R_ARROW_EQ | R_ARROW) ~ ArrowExprNoStruct)* }
|
||||
|
||||
ArrowExprNoStruct = { RangeExprNoStruct ~ ((THIN_R_ARROW | THIN_L_ARROW | FAT_D_ARROW | THIN_D_ARROW) ~ RangeExprNoStruct)* }
|
||||
|
||||
RangeExprNoStruct = { ShiftExprNoStruct ~ (DOT2 ~ ShiftExprNoStruct?)? }
|
||||
ShiftExprNoStruct = { AddExprNoStruct ~ ((L_ARROW2_EQ | L_ARROW2 | R_ARROW2_EQ | R_ARROW2) ~ AddExprNoStruct)* }
|
||||
AddExprNoStruct = { MulExprNoStruct ~ ((PLUS | MINUS) ~ MulExprNoStruct)* }
|
||||
MulExprNoStruct = { CastExprNoStruct ~ ((ASTERISK | SLASH | PERCENT) ~ CastExprNoStruct)* }
|
||||
CastExprNoStruct = { PrefixExprNoStruct ~ (KEYWORD_as ~ TypeExpr)* }
|
||||
PrefixExprNoStruct = { PrefixOp* ~ PostfixExprNoStruct }
|
||||
|
||||
PostfixExprNoStruct = {
|
||||
PrimaryExprNoStruct ~ (LPAREN ~ ExprList ~ RPAREN | DOT ~ IDENTIFIER | DOT ~ ASTERISK | LBRACKET ~ Expr ~ RBRACKET | LBRACKET ~ Expr ~ DOT2 ~ Expr? ~ RBRACKET)*
|
||||
}
|
||||
|
||||
PrimaryExprNoStruct = {
|
||||
FLOAT_LITERAL
|
||||
| INTEGER_LITERAL
|
||||
| CHAR_LITERAL
|
||||
| STRING_LITERAL
|
||||
| KEYWORD_true
|
||||
| KEYWORD_false
|
||||
| KEYWORD_null
|
||||
| TupleLiteral
|
||||
| AnonymousAggregateLiteral
|
||||
| ArrayLiteral
|
||||
| AnonymousArrayLiteral
|
||||
| VariantConstruct
|
||||
| ComptimeCallExpr
|
||||
| IDENTIFIER
|
||||
| Block
|
||||
| IfExpr
|
||||
| IfVarExpr
|
||||
| WhileExpr
|
||||
| LoopExpr
|
||||
| ForExpr
|
||||
| MatchExpr
|
||||
| FnTypeExpr
|
||||
}
|
||||
|
||||
VarBodyNoStruct = { Pattern ~ (COLON ~ TypeExpr)? ~ (EQUAL ~ ExprNoStruct)? }
|
||||
|
||||
TypeExpr = { HeaderList? ~ TypePrefixExpr }
|
||||
|
||||
TypePrefixExpr = { ASTERISK* ~ TypePostfixExpr }
|
||||
|
||||
TypePostfixExpr = {
|
||||
TypePrimary ~ (LPAREN ~ TypeExprList ~ RPAREN | DOT ~ IDENTIFIER | LBRACKET ~ TypeExpr ~ RBRACKET | KEYWORD_as ~ TypeExpr)*
|
||||
}
|
||||
|
||||
TypePrimary = {
|
||||
AT ~ IDENTIFIER // @self 等自引用/内置类型
|
||||
| ComptimeCallExpr // @import("...") 等导入即类型
|
||||
| IDENTIFIER
|
||||
| KEYWORD_i8
|
||||
| KEYWORD_u8
|
||||
| KEYWORD_i16
|
||||
| KEYWORD_u16
|
||||
| KEYWORD_i32
|
||||
| KEYWORD_u32
|
||||
| KEYWORD_i64
|
||||
| KEYWORD_u64
|
||||
| KEYWORD_f32
|
||||
| KEYWORD_f64
|
||||
| KEYWORD_isize
|
||||
| KEYWORD_usize
|
||||
| KEYWORD_bool
|
||||
| KEYWORD_void
|
||||
| KEYWORD_ptr
|
||||
| KEYWORD_type // `type` 元类型(编译期类型的值类型)
|
||||
| FnTypeExpr
|
||||
| AggregateTypeExpr
|
||||
| TypeParenExpr
|
||||
| ArrayTypeExpr
|
||||
}
|
||||
|
||||
ArrayTypeExpr = {
|
||||
LBRACKET ~ RBRACKET ~ TypeExpr
|
||||
| LBRACKET ~ (INTEGER_LITERAL | UNDERSCORE) ~ RBRACKET ~ TypeExpr
|
||||
}
|
||||
|
||||
TypeParenExpr = {
|
||||
LPAREN ~ RPAREN
|
||||
| LPAREN ~ TypeExpr ~ COMMA ~ (TypeExpr ~ COMMA)* ~ TypeExpr? ~ RPAREN
|
||||
| LPAREN ~ TypeExpr ~ RPAREN
|
||||
}
|
||||
|
||||
TypeExprList = { (TypeExpr ~ COMMA)* ~ TypeExpr? }
|
||||
|
||||
FnTypeExpr = { KEYWORD_comptime? ~ KEYWORD_fn ~ LPAREN ~ TypeExprList? ~ RPAREN ~ (COLON ~ TypeExpr)? }
|
||||
|
||||
AggregateTypeExpr = { (KEYWORD_struct | KEYWORD_union | KEYWORD_enum | KEYWORD_shape) ~ LBRACE ~ ContainerMembers ~ RBRACE }
|
||||
|
||||
IfExpr = { KEYWORD_if ~ ExprNoStruct ~ Block ~ (KEYWORD_else ~ (Block | IfExpr | IfVarExpr))? }
|
||||
IfVarExpr = { KEYWORD_if ~ KEYWORD_var ~ VarBodyNoStruct ~ Block ~ (KEYWORD_else ~ (Block | IfExpr | IfVarExpr))? }
|
||||
|
||||
WhileExpr = { Label? ~ KEYWORD_while ~ ExprNoStruct ~ Block ~ (KEYWORD_else ~ (Block | Expr))? }
|
||||
LoopExpr = { Label? ~ KEYWORD_loop ~ Block }
|
||||
ForExpr = { Label? ~ KEYWORD_for ~ Pattern ~ COLON ~ ExprNoStruct ~ (COMMA ~ ExprNoStruct)* ~ Block ~ (KEYWORD_else ~ (Block | Expr))? }
|
||||
|
||||
MatchExpr = { KEYWORD_match ~ ExprNoStruct ~ LBRACE ~ MatchArm* ~ RBRACE }
|
||||
MatchArm = { Pattern ~ FAT_R_ARROW ~ Block ~ COMMA? }
|
||||
|
||||
ComptimeCallExpr = { AT ~ IDENTIFIER ~ LPAREN ~ ExprList? ~ RPAREN }
|
||||
ParenExpr = {
|
||||
LPAREN ~ RPAREN
|
||||
| LPAREN ~ Expr ~ COMMA ~ (Expr ~ COMMA)* ~ Expr? ~ RPAREN
|
||||
| LPAREN ~ Expr ~ RPAREN
|
||||
}
|
||||
|
||||
AssignOp = { EQUAL | PLUS_EQ | MINUS_EQ | ASTERISK_EQ | SLASH_EQ | PERCENT_EQ | AMPERSAND_EQ | PIPE_EQ | CARET_EQ | L_ARROW2_EQ | R_ARROW2_EQ }
|
||||
BOOL_OR = { PIPE ~ PIPE }
|
||||
BOOL_AND = { AMPERSAND ~ AMPERSAND }
|
||||
EQ_EQ = { EQUAL ~ EQUAL }
|
||||
BANG_EQUAL = { BANG ~ EQUAL }
|
||||
|
||||
keyword_suffix = _{ !(ASCII_ALPHANUMERIC | "_") }
|
||||
KEYWORD_fn = @{ "fn" ~ keyword_suffix }
|
||||
KEYWORD_type = @{ "type" ~ keyword_suffix }
|
||||
KEYWORD_pub = @{ "pub" ~ keyword_suffix }
|
||||
KEYWORD_var = @{ "var" ~ keyword_suffix }
|
||||
KEYWORD_const = @{ "const" ~ keyword_suffix }
|
||||
KEYWORD_if = @{ "if" ~ keyword_suffix }
|
||||
KEYWORD_else = @{ "else" ~ keyword_suffix }
|
||||
KEYWORD_while = @{ "while" ~ keyword_suffix }
|
||||
KEYWORD_loop = @{ "loop" ~ keyword_suffix }
|
||||
KEYWORD_for = @{ "for" ~ keyword_suffix }
|
||||
KEYWORD_as = @{ "as" ~ keyword_suffix }
|
||||
KEYWORD_match = @{ "match" ~ keyword_suffix }
|
||||
KEYWORD_ret = @{ "ret" ~ keyword_suffix }
|
||||
KEYWORD_break = @{ "break" ~ keyword_suffix }
|
||||
KEYWORD_continue = @{ "continue" ~ keyword_suffix }
|
||||
KEYWORD_defer = @{ "defer" ~ keyword_suffix }
|
||||
KEYWORD_shape = @{ "shape" ~ keyword_suffix }
|
||||
KEYWORD_struct = @{ "struct" ~ keyword_suffix }
|
||||
KEYWORD_union = @{ "union" ~ keyword_suffix }
|
||||
KEYWORD_enum = @{ "enum" ~ keyword_suffix }
|
||||
KEYWORD_comptime = @{ "comptime" ~ keyword_suffix }
|
||||
KEYWORD_void = @{ "void" ~ keyword_suffix }
|
||||
KEYWORD_bool = @{ "bool" ~ keyword_suffix }
|
||||
KEYWORD_isize = @{ "isize" ~ keyword_suffix }
|
||||
KEYWORD_usize = @{ "usize" ~ keyword_suffix }
|
||||
KEYWORD_ptr = @{ "ptr" ~ keyword_suffix }
|
||||
KEYWORD_true = @{ "true" ~ keyword_suffix }
|
||||
KEYWORD_false = @{ "false" ~ keyword_suffix }
|
||||
KEYWORD_null = @{ "null" ~ keyword_suffix }
|
||||
KEYWORD_undefined = @{ "undefined" ~ keyword_suffix }
|
||||
KEYWORD_i8 = @{ "i8" ~ keyword_suffix }
|
||||
KEYWORD_u8 = @{ "u8" ~ keyword_suffix }
|
||||
KEYWORD_i16 = @{ "i16" ~ keyword_suffix }
|
||||
KEYWORD_u16 = @{ "u16" ~ keyword_suffix }
|
||||
KEYWORD_i32 = @{ "i32" ~ keyword_suffix }
|
||||
KEYWORD_u32 = @{ "u32" ~ keyword_suffix }
|
||||
KEYWORD_i64 = @{ "i64" ~ keyword_suffix }
|
||||
KEYWORD_u64 = @{ "u64" ~ keyword_suffix }
|
||||
KEYWORD_f32 = @{ "f32" ~ keyword_suffix }
|
||||
KEYWORD_f64 = @{ "f64" ~ keyword_suffix }
|
||||
|
||||
FAT_D_ARROW = { "<=>" }
|
||||
THIN_D_ARROW = { "<->" }
|
||||
L_ARROW2_EQ = { "<<=" }
|
||||
R_ARROW2_EQ = { ">>=" }
|
||||
DOT3 = { "..." }
|
||||
THIN_R_ARROW = { "<-" }
|
||||
THIN_L_ARROW = { "->" }
|
||||
L_ARROW_EQ = { "<=" }
|
||||
R_ARROW_EQ = { ">=" }
|
||||
L_ARROW2 = { "<<" }
|
||||
R_ARROW2 = { ">>" }
|
||||
FAT_R_ARROW = { "=>" }
|
||||
PLUS_EQ = { "+=" }
|
||||
MINUS_EQ = { "-=" }
|
||||
ASTERISK_EQ = { "*=" }
|
||||
SLASH_EQ = { "/=" }
|
||||
PERCENT_EQ = { "%=" }
|
||||
AMPERSAND_EQ = { "&=" }
|
||||
PIPE_EQ = { "|=" }
|
||||
CARET_EQ = { "^=" }
|
||||
DOT2 = { ".." }
|
||||
LPAREN = { "(" }
|
||||
RPAREN = { ")" }
|
||||
LBRACKET = { "[" }
|
||||
RBRACKET = { "]" }
|
||||
LBRACE = { "{" }
|
||||
RBRACE = { "}" }
|
||||
COMMA = { "," }
|
||||
SEMICOLON = { ";" }
|
||||
COLON = { ":" }
|
||||
DOT = { "." }
|
||||
AT = { "@" }
|
||||
SHARP = { "#" }
|
||||
DOLLAR = { "$" }
|
||||
EQUAL = { "=" }
|
||||
PLUS = { "+" }
|
||||
MINUS = { "-" }
|
||||
ASTERISK = { "*" }
|
||||
SLASH = { "/" }
|
||||
PERCENT = { "%" }
|
||||
AMPERSAND = { "&" }
|
||||
PIPE = { "|" }
|
||||
CARET = { "^" }
|
||||
TILDE = { "~" }
|
||||
BANG = { "!" }
|
||||
L_ARROW = { "<" }
|
||||
R_ARROW = { ">" }
|
||||
UNDERSCORE = { "_" }
|
||||
|
||||
keyword = _{
|
||||
KEYWORD_fn
|
||||
| KEYWORD_type
|
||||
| KEYWORD_var
|
||||
| KEYWORD_const
|
||||
| KEYWORD_if
|
||||
| KEYWORD_else
|
||||
| KEYWORD_while
|
||||
| KEYWORD_loop
|
||||
| KEYWORD_for
|
||||
| KEYWORD_as
|
||||
| KEYWORD_match
|
||||
| KEYWORD_ret
|
||||
| KEYWORD_break
|
||||
| KEYWORD_continue
|
||||
| KEYWORD_defer
|
||||
| KEYWORD_shape
|
||||
| KEYWORD_struct
|
||||
| KEYWORD_union
|
||||
| KEYWORD_enum
|
||||
| KEYWORD_comptime
|
||||
| KEYWORD_void
|
||||
| KEYWORD_bool
|
||||
| KEYWORD_true
|
||||
| KEYWORD_false
|
||||
| KEYWORD_null
|
||||
| KEYWORD_undefined
|
||||
| KEYWORD_i8
|
||||
| KEYWORD_u8
|
||||
| KEYWORD_i16
|
||||
| KEYWORD_u16
|
||||
| KEYWORD_i32
|
||||
| KEYWORD_u32
|
||||
| KEYWORD_i64
|
||||
| KEYWORD_u64
|
||||
| KEYWORD_f32
|
||||
| KEYWORD_f64
|
||||
| KEYWORD_isize
|
||||
| KEYWORD_usize
|
||||
| KEYWORD_ptr
|
||||
}
|
||||
IDENTIFIER = @{
|
||||
!(keyword ~ !(ASCII_ALPHANUMERIC | "_")) ~ (ASCII_ALPHA | "_" ~ (ASCII_ALPHANUMERIC | "_")) ~ (ASCII_ALPHANUMERIC | "_")*
|
||||
}
|
||||
|
||||
INTEGER_LITERAL = { DECIMAL | HEX | BINARY | OCTAL | "0" }
|
||||
DECIMAL = { ASCII_NONZERO_DIGIT ~ (ASCII_DIGIT | "_")* }
|
||||
HEX = { "0" ~ ("x" | "X") ~ ASCII_HEX_DIGIT ~ (ASCII_HEX_DIGIT | "_")* }
|
||||
BINARY = { "0" ~ ("b" | "B") ~ ASCII_BIN_DIGIT ~ (ASCII_BIN_DIGIT | "_")* }
|
||||
OCTAL = { "0" ~ ("o" | "O") ~ ASCII_OCT_DIGIT ~ (ASCII_OCT_DIGIT | "_")* }
|
||||
FLOAT_LITERAL = { ASCII_DIGIT ~ "." ~ ASCII_DIGIT ~ (ASCII_DIGIT | "_")* }
|
||||
CHAR_LITERAL = { "'" ~ (!"'" ~ ANY) ~ "'" }
|
||||
STRING_LITERAL = { "\"" ~ ANY* ~ "\"" }
|
||||
|
||||
COMMENT = _{ LINE_COMMENT | BLOCK_COMMENT }
|
||||
LINE_COMMENT = { "//" ~ (!"\n" ~ ANY)* }
|
||||
BLOCK_COMMENT = { "/*" ~ ANY* ~ "*/" }
|
||||
WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
|
||||
@@ -0,0 +1,896 @@
|
||||
|
||||
#define COMMON_CFLAGS "-Wall", "-Wextra", "-D_CRT_SECURE_NO_WARNINGS"
|
||||
#define SPL_CFLAGS COMMON_CFLAGS, "-O0", "-g"
|
||||
#define SPL_OPTIMIZATION_FLAG COMMON_CFLAGS, "-O2"
|
||||
#define nob_cc_flags(cmd) nob_cmd_append(cmd, SPL_CFLAGS)
|
||||
#undef UNICODE
|
||||
#define NOB_IMPLEMENTATION
|
||||
#include "nob.h"
|
||||
#undef NOB_IMPLEMENTATION
|
||||
#include "nob_popen.h"
|
||||
#ifdef _WIN32
|
||||
#include <consoleapi2.h>
|
||||
#endif
|
||||
#define BUILD_FOLDER "build/"
|
||||
|
||||
// Content of the .cflags stamp file; must match SPL_CFLAGS above.
|
||||
#define CFLAGS_STR "-Wall -Wextra -O0 -g"
|
||||
// Stamp file whose mtime is refreshed whenever the flags change so that all
|
||||
// object files get rebuilt.
|
||||
static const char *CFLAGS_STAMP = BUILD_FOLDER ".cflags";
|
||||
|
||||
/* clang-format off */
|
||||
#define VM_SRCS "stage0/spl_mcode.c", "stage0/spl_syscall.c", "stage0/spl_vm.c",
|
||||
static const char *spl_cli_srcs[] = {"stage0/spl_cli.c", VM_SRCS};
|
||||
static const char *vm_test_srcs[] = {"stage0/test_spl_vm.c", VM_SRCS};
|
||||
static const char *splc0_srcs[] = {
|
||||
"stage1/splc0.c",
|
||||
"stage1/spl_diag.c",
|
||||
"stage1/spl_tok.c",
|
||||
"stage1/spl_lexer.c",
|
||||
"stage1/spl_ast.c",
|
||||
"stage1/spl_parser.c",
|
||||
"stage1/spl_dumptree.c",
|
||||
"stage1/spl_type.c",
|
||||
"stage1/spl_fold.c",
|
||||
"stage1/spl_lang_type.c",
|
||||
"stage1/spl_attr.c",
|
||||
"stage1/spl_scope.c",
|
||||
"stage1/spl_sema.c",
|
||||
"stage1/spl_intrin.c",
|
||||
"stage1/spl_ir.c",
|
||||
"stage1/spl_ast2ir.c",
|
||||
"stage1/spl_layout.c",
|
||||
"stage1/spl_ir2vm.c",
|
||||
"stage1/ir_opt.c",
|
||||
"stage1/vm_opt.c",
|
||||
"stage0/spl_mcode.c",
|
||||
};
|
||||
|
||||
static const char* splc1_deps[] = {
|
||||
"stage2/sys/exit.spl",
|
||||
"stage2/sys/file.spl",
|
||||
"stage2/sys/mem.spl",
|
||||
"stage2/sys/spl_rt.h",
|
||||
"stage2/sys/spl_vm_embed.h",
|
||||
"stage2/sys/time.spl",
|
||||
"stage2/sys/vm.spl",
|
||||
"stage2/utils/algorithm.spl",
|
||||
"stage2/utils/arena.spl",
|
||||
"stage2/utils/ascii.spl",
|
||||
"stage2/utils/byte_buffer.spl",
|
||||
"stage2/utils/byte_view.spl",
|
||||
"stage2/utils/error.spl",
|
||||
"stage2/utils/fmt.spl",
|
||||
"stage2/utils/hash.spl",
|
||||
"stage2/utils/io.spl",
|
||||
"stage2/utils/json.spl",
|
||||
"stage2/utils/log.spl",
|
||||
"stage2/utils/lsp_frame.spl",
|
||||
"stage2/utils/map.spl",
|
||||
"stage2/utils/memory.spl",
|
||||
"stage2/utils/mp.spl",
|
||||
"stage2/utils/path.spl",
|
||||
"stage2/utils/profile.spl",
|
||||
"stage2/utils/set.spl",
|
||||
"stage2/utils/source.spl",
|
||||
"stage2/utils/str.spl",
|
||||
"stage2/utils/utf8.spl",
|
||||
"stage2/utils/value.spl",
|
||||
"stage2/utils/vec.spl",
|
||||
"stage2/ast.spl",
|
||||
"stage2/ast2ir.spl",
|
||||
"stage2/attr.spl",
|
||||
"stage2/def.spl",
|
||||
"stage2/diag.spl",
|
||||
"stage2/eval.spl",
|
||||
"stage2/intrin.spl",
|
||||
"stage2/ir.spl",
|
||||
"stage2/ir2c.spl",
|
||||
"stage2/ir2vm.spl",
|
||||
"stage2/ir_builder.spl",
|
||||
"stage2/lang_type.spl",
|
||||
"stage2/layout.spl",
|
||||
"stage2/lexer.spl",
|
||||
"stage2/mcode.spl",
|
||||
"stage2/parser.spl",
|
||||
"stage2/scope.spl",
|
||||
"stage2/sema.spl",
|
||||
"stage2/splc1.spl",
|
||||
"stage2/token.spl",
|
||||
"stage2/type_table.spl",
|
||||
};
|
||||
|
||||
static const char* splc2_deps[] = {
|
||||
|
||||
};
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
static const char *obj_path(const char *src) {
|
||||
// "stage0/spl_vm.c" -> "build/stage0_spl_vm.o"
|
||||
size_t prefix_len = strlen(BUILD_FOLDER);
|
||||
char *out = (char *)nob_temp_alloc(prefix_len + strlen(src) + 2 + 1);
|
||||
char *w = out;
|
||||
memcpy(w, BUILD_FOLDER, prefix_len);
|
||||
w += prefix_len;
|
||||
for (const char *p = src; *p && *p != '.'; ++p) {
|
||||
*w++ = (*p == '/' || *p == '\\') ? '_' : *p;
|
||||
}
|
||||
memcpy(w, ".o", 3);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Windows 上 clang/gcc 会输出 build/foo.exe;这里统一带上后缀,
|
||||
// 否则 nob_needs_rebuild 找不到输出会每次重新链接。
|
||||
static const char *exe_path(const char *name) {
|
||||
#ifdef _WIN32
|
||||
return nob_temp_sprintf(BUILD_FOLDER "%s.exe", name);
|
||||
#else
|
||||
return nob_temp_sprintf(BUILD_FOLDER "%s", name);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char *sir_path(const char *name) {
|
||||
return nob_temp_sprintf(BUILD_FOLDER "%s.sir", name);
|
||||
}
|
||||
|
||||
static const char *c_path(const char *name) { return nob_temp_sprintf(BUILD_FOLDER "%s.c", name); }
|
||||
|
||||
static bool sync_cflags_stamp(void) {
|
||||
Nob_String_View cflags = nob_sv_from_cstr(CFLAGS_STR);
|
||||
Nob_String_Builder old = {0};
|
||||
bool changed = true;
|
||||
if (nob_file_exists(CFLAGS_STAMP) && nob_read_entire_file(CFLAGS_STAMP, &old)) {
|
||||
changed = !nob_sv_eq(nob_sb_to_sv(old), cflags);
|
||||
}
|
||||
nob_sb_free(old);
|
||||
|
||||
if (changed) {
|
||||
if (!nob_write_entire_file(CFLAGS_STAMP, cflags.data, cflags.count)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prepare_build(void) {
|
||||
if (!nob_mkdir_if_not_exists(BUILD_FOLDER))
|
||||
return false;
|
||||
if (!sync_cflags_stamp())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct build {
|
||||
const char *name;
|
||||
enum {
|
||||
BUILD_EXE,
|
||||
BUILD_EXE_WITH_OPTIMIZATION,
|
||||
BUILD_SIR,
|
||||
BUILD_NATIVE,
|
||||
} kind;
|
||||
union {
|
||||
struct {
|
||||
const char **srcs;
|
||||
size_t srcs_count;
|
||||
const char **headers;
|
||||
size_t headers_count;
|
||||
} exe;
|
||||
struct {
|
||||
const char *src;
|
||||
const char *compiler;
|
||||
const char **deps;
|
||||
size_t deps_count;
|
||||
} sir;
|
||||
struct {
|
||||
const char *src;
|
||||
const char *compiler;
|
||||
const char **deps;
|
||||
size_t deps_count;
|
||||
} native;
|
||||
};
|
||||
} build_t;
|
||||
|
||||
typedef struct {
|
||||
const char **items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} str_da_t;
|
||||
|
||||
typedef struct run {
|
||||
const char *name;
|
||||
const char *interpreter;
|
||||
} run_t;
|
||||
|
||||
bool run_target(const char *name, Nob_Cmd *args);
|
||||
const char *build_target(const char *name);
|
||||
|
||||
static const char *build_exe(build_t *target) {
|
||||
Nob_Cmd cmd = {0};
|
||||
str_da_t objs = {0};
|
||||
str_da_t deps = {0};
|
||||
for (size_t i = 0; i < target->exe.srcs_count; ++i) {
|
||||
const char *src = target->exe.srcs[i];
|
||||
const char *obj = obj_path(src);
|
||||
if (!nob_file_exists(src)) {
|
||||
nob_log(NOB_ERROR, "source file `%s` does not exist", src);
|
||||
goto defer;
|
||||
}
|
||||
|
||||
da_append(&objs, obj);
|
||||
|
||||
da_append(&deps, src);
|
||||
da_append(&deps, CFLAGS_STAMP);
|
||||
for (size_t j = 0; j < target->exe.headers_count; ++j) {
|
||||
da_append(&deps, target->exe.headers[j]);
|
||||
}
|
||||
|
||||
int needs = nob_needs_rebuild(obj, deps.items, deps.count);
|
||||
deps.count = 0; // 清空 deps,下一轮循环重新收集
|
||||
|
||||
if (needs < 0)
|
||||
goto defer;
|
||||
if (needs == 0)
|
||||
continue;
|
||||
nob_cc(&cmd);
|
||||
nob_cc_flags(&cmd);
|
||||
nob_cmd_append(&cmd, "-c");
|
||||
nob_cc_inputs(&cmd, src);
|
||||
nob_cc_output(&cmd, obj);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "failed to compile `%s`", src);
|
||||
goto defer;
|
||||
}
|
||||
cmd.count = 0; // 清空 cmd,下一轮循环重新构建
|
||||
}
|
||||
|
||||
const char *exe = exe_path(target->name);
|
||||
|
||||
int needs = nob_needs_rebuild(exe, objs.items, objs.count);
|
||||
if (needs < 0)
|
||||
goto defer;
|
||||
if (needs == 0)
|
||||
return exe;
|
||||
|
||||
nob_cc(&cmd);
|
||||
nob_cmd_append(&cmd, SPL_OPTIMIZATION_FLAG);
|
||||
for (size_t i = 0; i < objs.count; ++i) {
|
||||
nob_cc_inputs(&cmd, objs.items[i]);
|
||||
}
|
||||
nob_cc_output(&cmd, exe);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
goto defer;
|
||||
}
|
||||
|
||||
return exe;
|
||||
defer:
|
||||
nob_cmd_free(cmd);
|
||||
da_free(objs);
|
||||
da_free(deps);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *build_native(const char *name) {
|
||||
const char *exe = exe_path(name);
|
||||
const char *cli = build_target("spl_cli");
|
||||
const char *sir = build_target("splc1");
|
||||
if (!cli || !sir) {
|
||||
nob_log(NOB_ERROR, "failed to build prerequisites for `%s`", name);
|
||||
return NULL;
|
||||
}
|
||||
const char *cpath = BUILD_FOLDER "splc1.c";
|
||||
const char *inputs[] = {sir,
|
||||
"stage2/sys/spl_rt.h",
|
||||
"stage2/sys/spl_vm_embed.h",
|
||||
"stage2/sys/spl_vm_embed.c",
|
||||
"stage0/spl_vm.c",
|
||||
"stage0/spl_mcode.c",
|
||||
"stage0/spl_syscall.c",
|
||||
"stage0/spl_vm.h",
|
||||
"stage0/spl_mcode.h"};
|
||||
int needs = nob_needs_rebuild(exe, inputs, NOB_ARRAY_LEN(inputs));
|
||||
if (needs < 0)
|
||||
return NULL;
|
||||
if (needs == 0)
|
||||
return exe;
|
||||
|
||||
Nob_Cmd cmd = {0};
|
||||
if (nob_file_exists(exe)) {
|
||||
// 已有原生编译器:用原生自举重新生成 C(0.1s 级)
|
||||
nob_cmd_append(&cmd, exe, "--dump", "c", "stage2/splc1.spl", cpath);
|
||||
} else {
|
||||
// 冷启动:用 VM 版 splc1 生成 C
|
||||
nob_cmd_append(&cmd, cli, sir, "--dump", "c", "stage2/splc1.spl", cpath);
|
||||
}
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "ir2c C generation failed");
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
cmd.count = 0;
|
||||
nob_cc(&cmd);
|
||||
nob_cc_inputs(&cmd, cpath);
|
||||
nob_cc_inputs(&cmd, "stage2/sys/spl_vm_embed.c");
|
||||
nob_cc_inputs(&cmd, "stage0/spl_vm.c");
|
||||
nob_cc_inputs(&cmd, "stage0/spl_mcode.c");
|
||||
nob_cc_inputs(&cmd, "stage0/spl_syscall.c");
|
||||
nob_cc_output(&cmd, exe);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "native link failed");
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
nob_cmd_free(cmd);
|
||||
return exe;
|
||||
}
|
||||
|
||||
static build_t targets[] = {
|
||||
{
|
||||
"spl_cli",
|
||||
BUILD_EXE_WITH_OPTIMIZATION,
|
||||
.exe = {.srcs = spl_cli_srcs,
|
||||
.srcs_count = NOB_ARRAY_LEN(spl_cli_srcs),
|
||||
.headers = NULL,
|
||||
.headers_count = 0},
|
||||
},
|
||||
{
|
||||
"spl_cli_g",
|
||||
BUILD_EXE,
|
||||
.exe = {.srcs = spl_cli_srcs,
|
||||
.srcs_count = NOB_ARRAY_LEN(spl_cli_srcs),
|
||||
.headers = NULL,
|
||||
.headers_count = 0},
|
||||
},
|
||||
{
|
||||
"vm_test",
|
||||
BUILD_EXE,
|
||||
.exe = {.srcs = vm_test_srcs,
|
||||
.srcs_count = NOB_ARRAY_LEN(vm_test_srcs),
|
||||
.headers = NULL,
|
||||
.headers_count = 0},
|
||||
},
|
||||
{
|
||||
"splc0",
|
||||
BUILD_EXE,
|
||||
.exe = {.srcs = splc0_srcs,
|
||||
.srcs_count = NOB_ARRAY_LEN(splc0_srcs),
|
||||
.headers = NULL,
|
||||
.headers_count = 0},
|
||||
},
|
||||
{
|
||||
"splc1",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc0",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc1s",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc1",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc1r",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc1s",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc1n",
|
||||
BUILD_NATIVE,
|
||||
.native = {.src = "stage2/splc1.spl",
|
||||
.compiler = "splc1",
|
||||
.deps = splc1_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc1_deps)},
|
||||
},
|
||||
{
|
||||
"splc2",
|
||||
BUILD_SIR,
|
||||
.sir = {.src = "stage3/splc2.spl",
|
||||
.compiler = "splc1n",
|
||||
.deps = splc2_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc2_deps)},
|
||||
},
|
||||
{
|
||||
"splc2n",
|
||||
BUILD_NATIVE,
|
||||
.native = {.src = "stage3/splc2.spl",
|
||||
.compiler = "splc1n",
|
||||
.deps = splc2_deps,
|
||||
.deps_count = NOB_ARRAY_LEN(splc2_deps)},
|
||||
},
|
||||
};
|
||||
|
||||
const char *build_target(const char *name) {
|
||||
if (!prepare_build())
|
||||
return NULL;
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(targets); ++i) {
|
||||
if (name != NULL && strcmp(targets[i].name, name) != 0) {
|
||||
continue;
|
||||
}
|
||||
build_t *target = &targets[i];
|
||||
switch (target->kind) {
|
||||
case BUILD_EXE_WITH_OPTIMIZATION:
|
||||
case BUILD_EXE: {
|
||||
return build_exe(target);
|
||||
}
|
||||
case BUILD_SIR: {
|
||||
Nob_Cmd cmd = {0};
|
||||
const char *target_sir = sir_path(name);
|
||||
nob_cmd_append(&cmd, target->sir.src, target_sir);
|
||||
if (!run_target(target->sir.compiler, &cmd)) {
|
||||
nob_log(NOB_ERROR, "failed to build `%s`", name);
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
nob_cmd_free(cmd);
|
||||
return target_sir;
|
||||
}
|
||||
case BUILD_NATIVE: {
|
||||
Nob_Cmd cmd = {0};
|
||||
const char *target_exe = exe_path(name);
|
||||
const char *target_c = c_path(name);
|
||||
|
||||
// TODO need_rebuild
|
||||
int needs =
|
||||
nob_needs_rebuild(target_exe, target->native.deps, target->native.deps_count);
|
||||
if (needs < 0)
|
||||
return NULL;
|
||||
if (needs == 0)
|
||||
return target_exe;
|
||||
|
||||
nob_cmd_append(&cmd, "--dump", "c", target->native.src, target_c);
|
||||
if (!run_target(target->native.compiler, &cmd)) {
|
||||
nob_log(NOB_ERROR, "failed to build `%s`", name);
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
cmd.count = 0;
|
||||
nob_cc(&cmd);
|
||||
nob_cmd_append(&cmd, "-I./stage2/sys", "-O2", "-D_CRT_SECURE_NO_WARNINGS");
|
||||
nob_cc_inputs(&cmd, target_c);
|
||||
nob_cc_output(&cmd, target_exe);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
nob_log(NOB_ERROR, "failed to build `%s`", name);
|
||||
nob_cmd_free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
nob_cmd_free(cmd);
|
||||
return target_exe;
|
||||
}
|
||||
default:
|
||||
NOB_UNREACHABLE("build_t.kind");
|
||||
}
|
||||
}
|
||||
nob_log(NOB_ERROR, "unknown build target `%s`", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static run_t runs[] = {
|
||||
{"spl_cli", NULL}, {"vm_test", NULL}, {"splc0", NULL},
|
||||
{"splc1", "spl_cli"}, {"splc1r", "spl_cli"}, {"splc1s", "spl_cli"},
|
||||
{"splc1n", NULL}, {"splc2", "spl_cli"}, {"splc2n", NULL},
|
||||
};
|
||||
|
||||
bool run_target_dry(const char *name, Nob_Cmd *args, Nob_Cmd *result) {
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(runs); ++i) {
|
||||
if (strcmp(runs[i].name, name) != 0) {
|
||||
continue;
|
||||
}
|
||||
const char *exe = build_target(runs[i].name);
|
||||
const char *interpreter = runs[i].interpreter;
|
||||
if (interpreter) {
|
||||
const char *inter = build_target(interpreter);
|
||||
if (!exe) {
|
||||
nob_log(NOB_ERROR, "failed to build interpreter `%s` for `%s`", interpreter, name);
|
||||
return false;
|
||||
}
|
||||
nob_cmd_append(result, inter);
|
||||
}
|
||||
nob_cmd_append(result, exe);
|
||||
nob_cmd_extend(result, args);
|
||||
return true;
|
||||
}
|
||||
nob_log(NOB_ERROR, "unknown run target `%s`", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool run_target(const char *name, Nob_Cmd *args) {
|
||||
Nob_Cmd result = {0};
|
||||
if (!run_target_dry(name, args, &result)) {
|
||||
return false;
|
||||
}
|
||||
nob_log(NOB_INFO, "running `%s`", name);
|
||||
bool ok = nob_cmd_run(&result);
|
||||
nob_cmd_free(result);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void parse_expect(const char *name, Nob_Cmd_Result *expect) {
|
||||
Nob_String_Builder sb = {0};
|
||||
nob_read_entire_file(name, &sb);
|
||||
Nob_String_View sv = nob_sb_to_sv(sb);
|
||||
while (sv.count > 0) {
|
||||
Nob_String_View line = nob_sv_chop_by_delim(&sv, '\n');
|
||||
line = nob_sv_trim_left(line); // 去掉前导空格
|
||||
|
||||
if (!nob_sv_starts_with(line, nob_sv_from_cstr("//"))) {
|
||||
break;
|
||||
}
|
||||
|
||||
Nob_String_View rest = nob_sv_from_parts(line.data + 2, line.count - 2);
|
||||
rest = nob_sv_trim_left(rest);
|
||||
|
||||
if (rest.count == 0)
|
||||
continue;
|
||||
|
||||
if (!nob_sv_starts_with(rest, nob_sv_from_cstr("Expect"))) {
|
||||
break;
|
||||
}
|
||||
rest = nob_sv_from_parts(rest.data + 6, rest.count - 6);
|
||||
rest = nob_sv_trim_left(rest);
|
||||
|
||||
// 按 '=' 分割
|
||||
Nob_String_View key = nob_sv_chop_by_delim(&rest, '=');
|
||||
key = nob_sv_trim(key);
|
||||
Nob_String_View value = nob_sv_trim(rest);
|
||||
|
||||
if (nob_sv_eq(key, nob_sv_from_cstr("retcode"))) {
|
||||
int val = 0;
|
||||
for (size_t i = 0; i < value.count && isdigit(value.data[i]); ++i) {
|
||||
val = val * 10 + (value.data[i] - '0');
|
||||
}
|
||||
// printf("expected code %d\n", val);
|
||||
expect->exit_code = val;
|
||||
} else if (nob_sv_eq(key, nob_sv_from_cstr("stdout"))) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool test_once(const char *fname, void *user_data) {
|
||||
(void)user_data;
|
||||
|
||||
const char *compiler_path = "splc0";
|
||||
const char *cli_path = "spl_cli";
|
||||
const char *target_sir = BUILD_FOLDER "test.sir";
|
||||
|
||||
Nob_Cmd cmd = {0};
|
||||
if (!build_target(compiler_path)) {
|
||||
return 1;
|
||||
}
|
||||
cmd.count = 0;
|
||||
|
||||
nob_cmd_append(&cmd, exe_path(compiler_path));
|
||||
nob_cmd_append(&cmd, fname, target_sir);
|
||||
if (!nob_cmd_run(&cmd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!build_target(cli_path)) {
|
||||
return 1;
|
||||
}
|
||||
cmd.count = 0;
|
||||
nob_cmd_append(&cmd, exe_path(cli_path));
|
||||
nob_cmd_append(&cmd, target_sir);
|
||||
// nob_cmd_run(&cmd);
|
||||
|
||||
Nob_Cmd_Result result = {0};
|
||||
Nob_String_Builder cmd_str = {0};
|
||||
nob_cmd_render(cmd, &cmd_str);
|
||||
|
||||
Nob_String_View sv = nob_sb_to_sv(cmd_str);
|
||||
nob_cmd_capture_popen(nob_temp_sv_to_cstr(sv), &result);
|
||||
// Expect retcode = number
|
||||
|
||||
Nob_Cmd_Result expect = {0};
|
||||
parse_expect(fname, &expect);
|
||||
|
||||
if (expect.exit_code != result.exit_code) {
|
||||
nob_log(NOB_ERROR, "Expect code `%d`, Result code `%d`", expect.exit_code,
|
||||
result.exit_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
nob_cmd_result_free(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_once_with_dump(const char *fname, void *user_data) {
|
||||
const char *target_name = "splc0";
|
||||
if (!prepare_build())
|
||||
return 1;
|
||||
if (!build_target(target_name))
|
||||
return 1;
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, exe_path(target_name));
|
||||
nob_cmd_append(&cmd, "--dump", (char *)user_data, fname, BUILD_FOLDER "t.sir");
|
||||
return nob_cmd_run(&cmd);
|
||||
}
|
||||
|
||||
// 由源路径派生构建产物路径:build/<basename>.sir
|
||||
static const char *artifact_sir(const char *src) {
|
||||
const char *base = src;
|
||||
for (const char *p = src; *p; ++p)
|
||||
if (*p == '/' || *p == '\\')
|
||||
base = p + 1;
|
||||
const char *dot = strrchr(base, '.');
|
||||
size_t len = dot ? (size_t)(dot - base) : strlen(base);
|
||||
return nob_temp_sprintf(BUILD_FOLDER "%.*s.sir", (int)len, base);
|
||||
}
|
||||
|
||||
static bool run_capture(Nob_Cmd *cmd, Nob_Cmd_Result *result) {
|
||||
Nob_String_Builder rendered = {0};
|
||||
nob_cmd_render(*cmd, &rendered);
|
||||
Nob_String_View sv = nob_sb_to_sv(rendered);
|
||||
bool ok = nob_cmd_capture_popen(nob_temp_sv_to_cstr(sv), result);
|
||||
nob_sb_free(rendered);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// diff 测试:对同一文件跑 `splc0 --dump <mode>` 与 `spl_cli splc1.sir --dump <mode>`,
|
||||
// 逐字节比较 stdout。
|
||||
static bool test_once_diff(const char *fname, void *user_data) {
|
||||
const char *mode = (const char *)user_data;
|
||||
if (!mode) {
|
||||
nob_log(NOB_ERROR, "diff test requires a mode: lex|ast|sema|ir|vm");
|
||||
return false;
|
||||
}
|
||||
if (!build_target("splc0"))
|
||||
return false;
|
||||
if (!build_target("spl_cli"))
|
||||
return false;
|
||||
if (!build_target("splc1"))
|
||||
return false;
|
||||
|
||||
Nob_Cmd c_cmd = {0};
|
||||
nob_cmd_append(&c_cmd, exe_path("splc0"), "--dump", (char *)mode, (char *)fname,
|
||||
BUILD_FOLDER "diff_c.sir");
|
||||
Nob_Cmd_Result c_res = {0};
|
||||
if (!run_capture(&c_cmd, &c_res)) {
|
||||
nob_cmd_result_free(c_res);
|
||||
return false;
|
||||
}
|
||||
|
||||
Nob_Cmd s_cmd = {0};
|
||||
nob_cmd_append(&s_cmd, exe_path("spl_cli"), BUILD_FOLDER "splc1.sir", "--dump", (char *)mode,
|
||||
(char *)fname);
|
||||
Nob_Cmd_Result s_res = {0};
|
||||
if (!run_capture(&s_cmd, &s_res)) {
|
||||
nob_cmd_result_free(c_res);
|
||||
nob_cmd_result_free(s_res);
|
||||
return false;
|
||||
}
|
||||
|
||||
Nob_String_View c_out = nob_sb_to_sv(c_res.out);
|
||||
Nob_String_View s_out = nob_sb_to_sv(s_res.out);
|
||||
bool same = nob_sv_eq(c_out, s_out);
|
||||
if (!same) {
|
||||
nob_log(NOB_ERROR, "diff mismatch: %s (c=%zu bytes, spl=%zu bytes)", fname, c_out.count,
|
||||
s_out.count);
|
||||
// 打印首个不相同行(按行比较)。
|
||||
Nob_String_View ca = c_out;
|
||||
Nob_String_View sa = s_out;
|
||||
size_t lineno = 1;
|
||||
while (ca.count > 0 || sa.count > 0) {
|
||||
Nob_String_View cl = nob_sv_chop_by_delim(&ca, '\n');
|
||||
Nob_String_View sl = nob_sv_chop_by_delim(&sa, '\n');
|
||||
if (!nob_sv_eq(cl, sl)) {
|
||||
nob_log(NOB_ERROR, " line %zu:", lineno);
|
||||
nob_log(NOB_ERROR, " c : %.*s", (int)cl.count, cl.data);
|
||||
nob_log(NOB_ERROR, " spl: %.*s", (int)sl.count, sl.data);
|
||||
break;
|
||||
}
|
||||
lineno++;
|
||||
}
|
||||
nob_log(NOB_ERROR, "--- c stderr/stdout merged above; exit=%d vs spl exit=%d ---",
|
||||
c_res.exit_code, s_res.exit_code);
|
||||
}
|
||||
|
||||
nob_cmd_result_free(c_res);
|
||||
nob_cmd_result_free(s_res);
|
||||
return same;
|
||||
}
|
||||
|
||||
static bool is_stage_name(const char *s) {
|
||||
return strcmp(s, "stage1") == 0 || strcmp(s, "stage2") == 0 || strcmp(s, "diff") == 0;
|
||||
}
|
||||
|
||||
static bool is_dump_mode(const char *s) {
|
||||
return strcmp(s, "lex") == 0 || strcmp(s, "ast") == 0 || strcmp(s, "sema") == 0 ||
|
||||
strcmp(s, "ir") == 0 || strcmp(s, "vm") == 0;
|
||||
}
|
||||
|
||||
static const char *stage_dir_of(const char *stage) {
|
||||
if (strcmp(stage, "stage2") == 0)
|
||||
return "./stage2/";
|
||||
return "./tests/stage1/";
|
||||
}
|
||||
|
||||
typedef bool (*test_fn_t)(const char *fname, void *user_data);
|
||||
typedef struct {
|
||||
Nob_Files *files;
|
||||
test_fn_t test_once;
|
||||
void *user_data;
|
||||
} test_walk_ctx_t;
|
||||
|
||||
static bool test_walk_entry(Nob_Walk_Entry entry) {
|
||||
if (entry.type != NOB_FILE_REGULAR)
|
||||
return true;
|
||||
|
||||
const char *dot = strrchr(entry.path, '.');
|
||||
if (!dot || strcmp(dot, ".spl") != 0)
|
||||
return true;
|
||||
|
||||
test_walk_ctx_t *ctx = (test_walk_ctx_t *)entry.data;
|
||||
bool run_all = (ctx->files->count == 0);
|
||||
bool matched = run_all;
|
||||
for (int i = 0; !matched && i < ctx->files->count; i += 1) {
|
||||
if (strstr(entry.path, ctx->files->items[i]))
|
||||
matched = true;
|
||||
}
|
||||
if (!matched)
|
||||
return true;
|
||||
|
||||
printf("--- test %s ---\n", entry.path);
|
||||
if (ctx->test_once(entry.path, ctx->user_data) == false)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool run_test(const char *stage, Nob_Files *files, test_fn_t test_once, void *user_data) {
|
||||
const char *test_folder = stage_dir_of(stage);
|
||||
|
||||
if (!prepare_build())
|
||||
return false;
|
||||
|
||||
if (!nob_file_exists(test_folder)) {
|
||||
nob_log(NOB_INFO, "%s does not exist, skip", test_folder);
|
||||
return true;
|
||||
}
|
||||
|
||||
test_walk_ctx_t ctx = {
|
||||
.files = files,
|
||||
.test_once = test_once,
|
||||
.user_data = user_data,
|
||||
};
|
||||
return nob_walk_dir(test_folder, test_walk_entry, .data = &ctx);
|
||||
}
|
||||
|
||||
static bool delete_walk_entry(Nob_Walk_Entry entry) { return nob_delete_file(entry.path); }
|
||||
|
||||
static bool delete_directory_recursively(const char *dir_path) {
|
||||
return nob_walk_dir(dir_path, delete_walk_entry, .post_order = true);
|
||||
}
|
||||
|
||||
static void print_help(const char *program_name) {
|
||||
nob_log(NOB_INFO, "SPL C-side bootstrap build system (nob)");
|
||||
nob_log(NOB_INFO, "%s", " ");
|
||||
nob_log(NOB_INFO, "Usage: %s <command> [args...]", program_name);
|
||||
nob_log(NOB_INFO, "%s", " ");
|
||||
nob_log(NOB_INFO, "Commands:");
|
||||
nob_log(NOB_INFO, " build [targets...] build all targets or the given ones");
|
||||
nob_log(NOB_INFO, " run <target> [args] build and run a target");
|
||||
nob_log(NOB_INFO, " test <kind> build and run tests");
|
||||
nob_log(NOB_INFO, " clean remove build artifacts");
|
||||
nob_log(NOB_INFO, " list list all targets");
|
||||
nob_log(NOB_INFO, " help/-h/--help print this help message");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
SetConsoleCP(CP_UTF8);
|
||||
#endif
|
||||
NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "nob.h", "nob_popen.h");
|
||||
set_log_handler(nob_cancer_log_handler);
|
||||
|
||||
const char *program_name = nob_shift(argv, argc);
|
||||
const char *command_name = "build";
|
||||
if (argc > 0) {
|
||||
command_name = nob_shift(argv, argc);
|
||||
}
|
||||
|
||||
if (strcmp(command_name, "build") == 0) {
|
||||
if (!prepare_build())
|
||||
return 1;
|
||||
if (argc > 0) {
|
||||
while (argc > 0) {
|
||||
const char *target_name = nob_shift(argv, argc);
|
||||
if (!build_target(target_name))
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (!build_target(NULL))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(command_name, "run") == 0) {
|
||||
if (argc <= 0) {
|
||||
nob_log(NOB_ERROR, "usage: %s run <target> [args...]", program_name);
|
||||
return 1;
|
||||
}
|
||||
const char *target_name = nob_shift(argv, argc);
|
||||
Nob_Cmd runcmd = {0};
|
||||
while (argc > 0) {
|
||||
nob_cmd_append(&runcmd, nob_shift(argv, argc));
|
||||
}
|
||||
return !run_target(target_name, &runcmd);
|
||||
}
|
||||
|
||||
if (strcmp(command_name, "test") == 0) {
|
||||
// 1. stage(可选,默认 stage1)
|
||||
const char *stage = "stage1";
|
||||
if (argc > 0 && is_stage_name(argv[0])) {
|
||||
stage = nob_shift(argv, argc);
|
||||
}
|
||||
|
||||
// 2. mode(可选)
|
||||
test_fn_t tfn = test_once;
|
||||
const char *mode_name = NULL;
|
||||
if (argc > 0) {
|
||||
if (is_dump_mode(argv[0])) {
|
||||
tfn = test_once_with_dump;
|
||||
mode_name = nob_shift(argv, argc);
|
||||
} else if (strcmp(argv[0], "run") == 0) {
|
||||
nob_shift(argv, argc);
|
||||
// HACK:只对 stage1 有意义,跑上次生成的 build/test.sir
|
||||
Nob_Cmd cmd = {0};
|
||||
nob_cmd_append(&cmd, BUILD_FOLDER "spl_cli", "-d");
|
||||
nob_cmd_append(&cmd, BUILD_FOLDER "test.sir");
|
||||
return nob_cmd_run(&cmd) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// diff 需要显式 mode,且使用专门的双编译器比较函数。
|
||||
if (strcmp(stage, "diff") == 0) {
|
||||
if (!mode_name) {
|
||||
nob_log(NOB_ERROR, "diff requires a mode: lex|ast|sema|ir|vm");
|
||||
return 1;
|
||||
}
|
||||
tfn = test_once_diff;
|
||||
}
|
||||
|
||||
// 3. 剩下的都当 pattern
|
||||
Nob_Files files = {0};
|
||||
while (argc > 0) {
|
||||
da_append(&files, nob_shift(argv, argc));
|
||||
}
|
||||
|
||||
return run_test(stage, &files, tfn, (void *)mode_name) ? 0 : 1;
|
||||
}
|
||||
|
||||
if (strcmp(command_name, "clean") == 0) {
|
||||
if (nob_file_exists(BUILD_FOLDER)) {
|
||||
if (!delete_directory_recursively(BUILD_FOLDER))
|
||||
return 1;
|
||||
}
|
||||
nob_log(NOB_INFO, "cleaned %s", BUILD_FOLDER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(command_name, "list") == 0) {
|
||||
for (size_t i = 0; i < NOB_ARRAY_LEN(targets); ++i) {
|
||||
nob_log(NOB_INFO, "%s:", targets[i].name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(command_name, "help") == 0 || strcmp(command_name, "-h") == 0 ||
|
||||
strcmp(command_name, "--help") == 0) {
|
||||
print_help(program_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
nob_log(NOB_ERROR, "unknown command `%s`", command_name);
|
||||
print_help(program_name);
|
||||
return 1;
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
#ifndef __NOB_POPEN_H__
|
||||
#define __NOB_POPEN_H__
|
||||
|
||||
#include "nob.h"
|
||||
|
||||
typedef struct {
|
||||
Nob_String_Builder out; // stdout 内容
|
||||
int exit_code; // 实际退出码
|
||||
bool ok; // 是否成功启动
|
||||
} Nob_Cmd_Result;
|
||||
|
||||
typedef struct {
|
||||
const char **items;
|
||||
int capacity;
|
||||
int count;
|
||||
} Nob_Files;
|
||||
|
||||
// 运行命令,捕获 stdout(Windows 下会合并 stderr)和真实退出码。
|
||||
// 返回 true 仅表示命令成功启动并完成,不代表 exit_code == 0。
|
||||
bool nob_cmd_capture_popen(const char *tname, Nob_Cmd_Result *res) {
|
||||
memset(res, 0, sizeof(*res));
|
||||
|
||||
#ifdef _WIN32
|
||||
// ---------- Windows: CreateProcessA + 匿名管道 ----------
|
||||
SECURITY_ATTRIBUTES sa = {0};
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
|
||||
HANDLE hRead = NULL;
|
||||
HANDLE hWrite = NULL;
|
||||
if (!CreatePipe(&hRead, &hWrite, &sa, 0)) {
|
||||
nob_log(NOB_ERROR, "CreatePipe failed: %s", nob_win32_error_message(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读端不能被继承,否则父进程 ReadFile 永远等不到 EOF
|
||||
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) {
|
||||
nob_log(NOB_ERROR, "SetHandleInformation failed: %s",
|
||||
nob_win32_error_message(GetLastError()));
|
||||
CloseHandle(hRead);
|
||||
CloseHandle(hWrite);
|
||||
return false;
|
||||
}
|
||||
|
||||
STARTUPINFOA si = {0};
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
si.hStdOutput = hWrite;
|
||||
si.hStdError = hWrite; // 把 stderr 也并入 out,需要分离的话另开一条管道
|
||||
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
// CreateProcessA 会就地修改 lpCommandLine,必须传可写缓冲
|
||||
char *cmdline = NULL;
|
||||
{
|
||||
size_t n = strlen(tname) + 1;
|
||||
cmdline = (char *)malloc(n);
|
||||
if (!cmdline) {
|
||||
CloseHandle(hRead);
|
||||
CloseHandle(hWrite);
|
||||
nob_log(NOB_ERROR, "malloc failed");
|
||||
return false;
|
||||
}
|
||||
memcpy(cmdline, tname, n);
|
||||
}
|
||||
|
||||
BOOL ok = CreateProcessA(NULL, // lpApplicationName: 让系统从 lpCommandLine 里解析
|
||||
cmdline, // lpCommandLine
|
||||
NULL, // lpProcessAttributes
|
||||
NULL, // lpThreadAttributes
|
||||
TRUE, // bInheritHandles: 必须为 TRUE,才能继承管道写端
|
||||
CREATE_NO_WINDOW, // 不弹黑窗;想弹就传 0
|
||||
NULL, // lpEnvironment
|
||||
NULL, // lpCurrentDirectory
|
||||
&si, &pi);
|
||||
|
||||
free(cmdline);
|
||||
|
||||
// 无论成功与否,父进程都不再需要写端(子进程已持有自己的副本)
|
||||
CloseHandle(hWrite);
|
||||
|
||||
if (!ok) {
|
||||
nob_log(NOB_ERROR, "CreateProcessA failed for \"%s\": %s", tname,
|
||||
nob_win32_error_message(GetLastError()));
|
||||
CloseHandle(hRead);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------- 读取 stdout ----------
|
||||
{
|
||||
char buffer[4096];
|
||||
DWORD n = 0;
|
||||
while (ReadFile(hRead, buffer, sizeof(buffer), &n, NULL) && n > 0) {
|
||||
nob_sb_append_buf(&res->out, buffer, (size_t)n);
|
||||
}
|
||||
}
|
||||
CloseHandle(hRead);
|
||||
|
||||
// ---------- 等待并取真实退出码 ----------
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
DWORD code = 0;
|
||||
if (!GetExitCodeProcess(pi.hProcess, &code)) {
|
||||
nob_log(NOB_ERROR, "GetExitCodeProcess failed: %s",
|
||||
nob_win32_error_message(GetLastError()));
|
||||
code = (DWORD)-1;
|
||||
}
|
||||
res->exit_code = (int)code;
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
#else
|
||||
// ---------- POSIX: popen ----------
|
||||
FILE *fp = popen(tname, "r");
|
||||
if (!fp) {
|
||||
nob_log(NOB_ERROR, "popen failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
char buffer[4096];
|
||||
while (fgets(buffer, sizeof(buffer), fp)) {
|
||||
nob_sb_append_cstr(&res->out, buffer);
|
||||
}
|
||||
|
||||
int status = pclose(fp);
|
||||
if (status == -1) {
|
||||
nob_log(NOB_ERROR, "pclose failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (WIFEXITED(status))
|
||||
status = WEXITSTATUS(status);
|
||||
else if (WIFSIGNALED(status))
|
||||
status = 128 + WTERMSIG(status);
|
||||
res->exit_code = status;
|
||||
#endif
|
||||
|
||||
res->ok = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define nob_cmd_result_free(res) nob_sb_free((res).out)
|
||||
|
||||
#endif /* __NOB_POPEN_H__ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file color.h
|
||||
* @brief ANSI终端颜色控制码定义
|
||||
*
|
||||
* 提供跨平台的终端文本颜色和样式控制支持
|
||||
*/
|
||||
|
||||
#ifndef __SCC_TERMINAL_COLOR_H__
|
||||
#define __SCC_TERMINAL_COLOR_H__
|
||||
|
||||
/* clang-format off */
|
||||
/// @name 前景色控制码
|
||||
/// @{
|
||||
#define ANSI_FG_BLACK "\33[30m" ///< 黑色前景
|
||||
#define ANSI_FG_RED "\33[31m" ///< 红色前景
|
||||
#define ANSI_FG_GREEN "\33[32m" ///< 绿色前景
|
||||
#define ANSI_FG_YELLOW "\33[33m" ///< 黄色前景
|
||||
#define ANSI_FG_BLUE "\33[34m" ///< 蓝色前景
|
||||
#define ANSI_FG_MAGENTA "\33[35m" ///< 品红色前景
|
||||
#define ANSI_FG_CYAN "\33[36m" ///< 青色前景
|
||||
#define ANSI_FG_WHITE "\33[37m" ///< 白色前景
|
||||
/// @}
|
||||
|
||||
/// @name 背景色控制码
|
||||
/// @{
|
||||
#define ANSI_BG_BLACK "\33[40m" ///< 黑色背景
|
||||
#define ANSI_BG_RED "\33[41m" ///< 红色背景
|
||||
#define ANSI_BG_GREEN "\33[42m" ///< 绿色背景
|
||||
#define ANSI_BG_YELLOW "\33[43m" ///< 黄色背景
|
||||
#define ANSI_BG_BLUE "\33[44m" ///< 蓝色背景
|
||||
#define ANSI_BG_MAGENTA "\33[45m" ///< 品红色背景(注:原始代码此处应为45m)
|
||||
#define ANSI_BG_CYAN "\33[46m" ///< 青色背景
|
||||
#define ANSI_BG_WHITE "\33[47m" ///< 白色背景
|
||||
/// @}
|
||||
|
||||
/// @name 文字样式控制码
|
||||
/// @{
|
||||
#define ANSI_UNDERLINED "\33[4m" ///< 下划线样式
|
||||
#define ANSI_BOLD "\33[1m" ///< 粗体样式
|
||||
#define ANSI_NONE "\33[0m" ///< 重置所有样式
|
||||
/// @}
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
* @def ANSI_FMT
|
||||
* @brief 安全文本格式化宏
|
||||
* @param str 目标字符串
|
||||
* @param fmt ANSI格式序列(可组合多个样式)
|
||||
*
|
||||
* @note 当定义ANSI_FMT_DISABLE时自动禁用颜色输出
|
||||
* @code
|
||||
* printf(ANSI_FMT("Warning!", ANSI_FG_YELLOW ANSI_BOLD));
|
||||
* @endcode
|
||||
*/
|
||||
#ifndef ANSI_FMT_DISABLE
|
||||
#define ANSI_FMT(str, fmt) fmt str ANSI_NONE ///< 启用样式包裹
|
||||
#else
|
||||
#define ANSI_FMT(str, fmt) str ///< 禁用样式输出
|
||||
#endif
|
||||
|
||||
#endif /* __SCC_TERMINAL_COLOR_H__ */
|
||||
@@ -0,0 +1,225 @@
|
||||
#ifndef __CORE_MAP_H__
|
||||
#define __CORE_MAP_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef nullptr
|
||||
#define nullptr NULL
|
||||
#endif
|
||||
typedef uintptr_t usize;
|
||||
typedef intptr_t isize;
|
||||
|
||||
#define MAP_TYPEOF __typeof__
|
||||
|
||||
/* 状态常量 */
|
||||
#define __MAP_SLOT_EMPTY 0
|
||||
#define __MAP_SLOT_OCCUPIED 1
|
||||
#define __MAP_SLOT_DELETED 2
|
||||
|
||||
/* 默认负载因子 factor/128 */
|
||||
#define MAP_DEFAULT_LOAD_FACTOR 70
|
||||
|
||||
/* ---------- 默认哈希/比较函数 ---------- */
|
||||
#define MAP_HASH_INT(k) ((usize)((k) * 2654435761U))
|
||||
#define MAP_CMP_INT(a, b) ((a) != (b))
|
||||
|
||||
static inline usize map_hash_str(const char *s) {
|
||||
usize h = 5381;
|
||||
while (*s)
|
||||
h = ((h << 5) + h) + (unsigned char)*s++;
|
||||
return h;
|
||||
}
|
||||
#define MAP_HASH_STR map_hash_str
|
||||
#define MAP_CMP_STR strcmp
|
||||
|
||||
/* ---------- 数据结构宏 ---------- */
|
||||
#define MAP_SLOT(key_t, val_t) \
|
||||
struct { \
|
||||
key_t key; \
|
||||
val_t val; \
|
||||
char state; \
|
||||
}
|
||||
|
||||
#define MAP(key_t, val_t) \
|
||||
struct { \
|
||||
usize size; \
|
||||
usize cap; \
|
||||
MAP_SLOT(key_t, val_t) * data; \
|
||||
usize (*hash)(key_t); \
|
||||
int (*cmp)(key_t, key_t); \
|
||||
}
|
||||
|
||||
/* ---------- 操作宏 ---------- */
|
||||
|
||||
/** 初始化,必须提供哈希和比较函数 */
|
||||
#define map_init(map, hash_fn, cmp_fn) \
|
||||
do { \
|
||||
(map).size = 0; \
|
||||
(map).cap = 0; \
|
||||
(map).data = nullptr; \
|
||||
(map).hash = (hash_fn); \
|
||||
(map).cmp = (cmp_fn); \
|
||||
} while (0)
|
||||
|
||||
/** 释放内部数组 */
|
||||
#define map_free(map) \
|
||||
do { \
|
||||
free((map).data); \
|
||||
(map).data = nullptr; \
|
||||
(map).size = (map).cap = 0; \
|
||||
} while (0)
|
||||
|
||||
/** 遍历所有有效元素 */
|
||||
#define map_for(map, idx) \
|
||||
for (usize(idx) = 0; (idx) < (map).cap; ++(idx)) \
|
||||
if ((map).data[(idx)].state == __MAP_SLOT_OCCUPIED)
|
||||
|
||||
#define unsafe_map_at(map, idx) ((map).data[(idx)])
|
||||
|
||||
/**
|
||||
* 插入(若键已存在则更新值)
|
||||
* 注意:扩容使用 realloc,失败会 abort(可自行修改错误处理)
|
||||
*/
|
||||
#define map_put(map, _key, _val) \
|
||||
do { \
|
||||
/* 扩容 */ \
|
||||
if ((map).cap == 0 || (map).size * 128 / (map).cap >= MAP_DEFAULT_LOAD_FACTOR) { \
|
||||
usize new_cap = (map).cap == 0 ? 8 : (map).cap * 2; \
|
||||
MAP_SLOT(MAP_TYPEOF((map).data->key), MAP_TYPEOF((map).data->val)) *new_data = \
|
||||
calloc(new_cap, sizeof(*new_data)); \
|
||||
if (!new_data) \
|
||||
abort(); \
|
||||
/* 重新插入旧元素 */ \
|
||||
for (usize _i = 0; _i < (map).cap; ++_i) { \
|
||||
if ((map).data[_i].state == __MAP_SLOT_OCCUPIED) { \
|
||||
usize _h = (map).hash((map).data[_i].key) & (new_cap - 1); \
|
||||
while (new_data[_h].state == __MAP_SLOT_OCCUPIED) \
|
||||
_h = (_h + 1) & (new_cap - 1); \
|
||||
new_data[_h].key = (map).data[_i].key; \
|
||||
new_data[_h].val = (map).data[_i].val; \
|
||||
new_data[_h].state = __MAP_SLOT_OCCUPIED; \
|
||||
} \
|
||||
} \
|
||||
free((map).data); \
|
||||
(map).data = (void *)new_data; \
|
||||
(map).cap = new_cap; \
|
||||
} \
|
||||
/* 查找或插入 */ \
|
||||
usize _mask = (map).cap - 1; \
|
||||
usize _idx = (map).hash(_key) & _mask; \
|
||||
usize _first_del = (usize) - 1; \
|
||||
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
|
||||
(map).cmp((map).data[_idx].key, _key) == 0) { \
|
||||
(map).data[_idx].val = _val; \
|
||||
break; \
|
||||
} \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_DELETED && _first_del == (usize) - 1) \
|
||||
_first_del = _idx; \
|
||||
_idx = (_idx + 1) & _mask; \
|
||||
} \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_EMPTY) { \
|
||||
usize _target = (_first_del != (usize) - 1) ? _first_del : _idx; \
|
||||
(map).data[_target].key = _key; \
|
||||
(map).data[_target].val = _val; \
|
||||
(map).data[_target].state = __MAP_SLOT_OCCUPIED; \
|
||||
++(map).size; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* 插入(仅当键不存在时),若键已存在则静默跳过,不更新值
|
||||
* 与 map_put 的差异仅在"键已存在时":map_put 更新值,map_put_new 放弃
|
||||
*/
|
||||
#define map_put_new(map, _key, _val) \
|
||||
do { \
|
||||
/* 扩容 */ \
|
||||
if ((map).cap == 0 || (map).size * 128 / (map).cap >= MAP_DEFAULT_LOAD_FACTOR) { \
|
||||
usize new_cap = (map).cap == 0 ? 8 : (map).cap * 2; \
|
||||
MAP_SLOT(MAP_TYPEOF((map).data->key), MAP_TYPEOF((map).data->val)) *new_data = \
|
||||
calloc(new_cap, sizeof(*new_data)); \
|
||||
if (!new_data) \
|
||||
abort(); \
|
||||
/* 重新插入旧元素 */ \
|
||||
for (usize _i = 0; _i < (map).cap; ++_i) { \
|
||||
if ((map).data[_i].state == __MAP_SLOT_OCCUPIED) { \
|
||||
usize _h = (map).hash((map).data[_i].key) & (new_cap - 1); \
|
||||
while (new_data[_h].state == __MAP_SLOT_OCCUPIED) \
|
||||
_h = (_h + 1) & (new_cap - 1); \
|
||||
new_data[_h].key = (map).data[_i].key; \
|
||||
new_data[_h].val = (map).data[_i].val; \
|
||||
new_data[_h].state = __MAP_SLOT_OCCUPIED; \
|
||||
} \
|
||||
} \
|
||||
free((map).data); \
|
||||
(map).data = (void *)new_data; \
|
||||
(map).cap = new_cap; \
|
||||
} \
|
||||
/* 查找 */ \
|
||||
usize _mask = (map).cap - 1; \
|
||||
usize _idx = (map).hash(_key) & _mask; \
|
||||
usize _first_del = (usize) - 1; \
|
||||
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
|
||||
(map).cmp((map).data[_idx].key, _key) == 0) { \
|
||||
break; /* 已存在,不更新 */ \
|
||||
} \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_DELETED && _first_del == (usize) - 1) \
|
||||
_first_del = _idx; \
|
||||
_idx = (_idx + 1) & _mask; \
|
||||
} \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_EMPTY) { \
|
||||
usize _target = (_first_del != (usize) - 1) ? _first_del : _idx; \
|
||||
(map).data[_target].key = _key; \
|
||||
(map).data[_target].val = _val; \
|
||||
(map).data[_target].state = __MAP_SLOT_OCCUPIED; \
|
||||
++(map).size; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* 查询:若找到,*out_val 被赋值为对应值并返回 1;否则返回 0
|
||||
*/
|
||||
#define map_get(map, _key, out_val) \
|
||||
(({ \
|
||||
int _found = 0; \
|
||||
if ((map).cap > 0) { \
|
||||
usize _mask = (map).cap - 1; \
|
||||
usize _idx = (map).hash(_key) & _mask; \
|
||||
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
|
||||
(map).cmp((map).data[_idx].key, _key) == 0) { \
|
||||
*out_val = (map).data[_idx].val; \
|
||||
_found = 1; \
|
||||
break; \
|
||||
} \
|
||||
_idx = (_idx + 1) & _mask; \
|
||||
} \
|
||||
} \
|
||||
_found; \
|
||||
}))
|
||||
|
||||
/**
|
||||
* 删除指定键
|
||||
*/
|
||||
#define map_del(map, _key) \
|
||||
do { \
|
||||
if ((map).cap == 0) \
|
||||
break; \
|
||||
usize _mask = (map).cap - 1; \
|
||||
usize _idx = (map).hash(_key) & _mask; \
|
||||
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
|
||||
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
|
||||
(map).cmp((map).data[_idx].key, _key) == 0) { \
|
||||
(map).data[_idx].state = __MAP_SLOT_DELETED; \
|
||||
--(map).size; \
|
||||
break; \
|
||||
} \
|
||||
_idx = (_idx + 1) & _mask; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif /* __CORE_MAP_H__ */
|
||||
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* @file vec.h
|
||||
* @brief 动态数组(Dynamic Array)实现
|
||||
*
|
||||
* 提供类型安全的动态数组容器实现,支持自动扩容和基本操作
|
||||
*/
|
||||
|
||||
#ifndef __CORE_VEC_H__
|
||||
#define __CORE_VEC_H__
|
||||
|
||||
#define __CORE_VEC_USE_STD__
|
||||
#ifndef __CORE_VEC_USE_STD__
|
||||
#include "core_log.h"
|
||||
|
||||
#include "core_impl.h"
|
||||
#include "core_type.h"
|
||||
#define __vec_realloc realloc
|
||||
#define __vec_free free
|
||||
#define __vec_memcpy memcpy
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef nullptr
|
||||
#define nullptr NULL
|
||||
#endif
|
||||
typedef size_t usize;
|
||||
#define __vec_realloc realloc
|
||||
#define __vec_free free
|
||||
#define __vec_memcpy memcpy
|
||||
|
||||
#ifndef LOG_FATAL
|
||||
#include <stdio.h>
|
||||
#define LOG_FATAL(...) \
|
||||
do { \
|
||||
printf(__VA_ARGS__); \
|
||||
abort(); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef Assert
|
||||
#include <assert.h>
|
||||
#define Assert(cond) assert(cond)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** @defgroup vec_struct 数据结构定义 */
|
||||
|
||||
/**
|
||||
* @def VEC(type)
|
||||
* @brief 声明向量结构体
|
||||
* @param type 存储的数据类型
|
||||
*
|
||||
* 生成包含size/cap/data三个字段的结构体定义:
|
||||
* - size: 当前元素数量
|
||||
* - cap: 数组容量
|
||||
* - data: 存储数组指针
|
||||
* @example
|
||||
* VEC(char) string; <=> char[dynamic_array] string;
|
||||
* struct people { VEC(char) name; int age; VEC(struct people) children;
|
||||
* };
|
||||
*/
|
||||
#define VEC(type) \
|
||||
struct { \
|
||||
usize size; \
|
||||
usize cap; \
|
||||
type *data; \
|
||||
}
|
||||
|
||||
/** @defgroup vec_operations 动态数组操作宏 */
|
||||
|
||||
/**
|
||||
* @def vec_init(vec)
|
||||
* @brief 初始化向量结构体
|
||||
* @param vec 要初始化的向量结构体变量
|
||||
*
|
||||
* @note 此宏不会分配内存,仅做零初始化
|
||||
*/
|
||||
#define vec_init(vec) \
|
||||
do { \
|
||||
(vec).size = 0, (vec).cap = 0, (vec).data = 0; \
|
||||
} while (0)
|
||||
|
||||
#define vec_realloc(vec, new_cap) \
|
||||
do { \
|
||||
void *data = __vec_realloc((vec).data, new_cap * sizeof(*(vec).data)); \
|
||||
if (!data) { \
|
||||
LOG_FATAL("vector_push: realloc failed\n"); \
|
||||
} \
|
||||
(vec).cap = new_cap; \
|
||||
(vec).data = data; \
|
||||
} while (0)
|
||||
|
||||
#define vec_size(vec) ((vec).size)
|
||||
#define vec_cap(vec) ((vec).cap)
|
||||
#define vec_for(vec, idx) for (usize idx = 0; idx < vec_size(vec); idx += 1)
|
||||
|
||||
/**
|
||||
* @def vec_push(vec, value)
|
||||
* @brief 添加元素到向量末尾
|
||||
* @param vec 目标向量结构体
|
||||
* @param value 要添加的值(需匹配存储类型)
|
||||
*
|
||||
* @note 当容量不足时自动扩容为2倍(初始容量为4)
|
||||
* @warning 内存分配失败时会触发LOG_FATAL
|
||||
*/
|
||||
#define vec_push(vec, value) \
|
||||
do { \
|
||||
if ((vec).size >= (vec).cap) { \
|
||||
usize cap = (vec).cap ? (vec).cap * 2 : 4; \
|
||||
vec_realloc(vec, cap); \
|
||||
} \
|
||||
Assert((vec).data != nullptr); \
|
||||
(vec).data[(vec).size++] = value; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @def vec_pop(vec)
|
||||
* @brief 弹出最后一个元素
|
||||
* @param vec 目标向量结构体
|
||||
* @return 最后元素的引用
|
||||
* @warning 需确保size > 0时使用
|
||||
*/
|
||||
#define vec_pop(vec) ((vec).data[--(vec).size])
|
||||
|
||||
/**
|
||||
* @def vec_at(vec, idx)
|
||||
* @brief 获取指定索引元素
|
||||
* @param vec 目标向量结构体
|
||||
* @param idx 元素索引(0 <= idx < size)
|
||||
* @return 对应元素的引用
|
||||
*/
|
||||
#define vec_at(vec, idx) (((vec).data)[idx])
|
||||
|
||||
/**
|
||||
* @def vec_idx(vec, ptr)
|
||||
* @brief 获取元素指针对应的索引
|
||||
* @param vec 目标向量结构体
|
||||
* @param ptr 元素指针(需在data数组范围内)
|
||||
* @return 元素索引值
|
||||
*/
|
||||
#define vec_idx(vec, ptr) ((ptr) - (vec).data)
|
||||
|
||||
/**
|
||||
* @def vec_free(vec)
|
||||
* @brief 释放向量内存
|
||||
* @param vec 目标向量结构体
|
||||
*
|
||||
* @note 释放后需重新初始化才能再次使用
|
||||
*/
|
||||
#define vec_free(vec) \
|
||||
do { \
|
||||
if ((vec).data == nullptr) \
|
||||
break; \
|
||||
__vec_free((vec).data); \
|
||||
(vec).data = nullptr; \
|
||||
(vec).size = (vec).cap = 0; \
|
||||
} while (0)
|
||||
|
||||
#define vec_unsafe_get_data(vec) ((vec).data)
|
||||
|
||||
#define vec_unsafe_from_buffer(vec, buffer, buffer_size) \
|
||||
do { \
|
||||
(vec).size = buffer_size; \
|
||||
(vec).cap = (vec).size; \
|
||||
(vec).data = buffer; \
|
||||
} while (0)
|
||||
|
||||
#define vec_unsafe_from_static_array(vec, array) \
|
||||
do { \
|
||||
(vec).size = sizeof(array) / sizeof((array)[0]); \
|
||||
(vec).cap = (vec).size; \
|
||||
(vec).data = array; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @def vec_sized_realloc(vec, elem_size, new_cap)
|
||||
* @brief 内部宏:按 elem_size 重新分配内存
|
||||
*/
|
||||
#define vec_sized_realloc(vec, elem_size, new_cap) \
|
||||
do { \
|
||||
void *new_data = __vec_realloc((vec).data, (new_cap) * (elem_size)); \
|
||||
if (!new_data) \
|
||||
LOG_FATAL("vec_sized_realloc: failed\n"); \
|
||||
(vec).data = new_data; \
|
||||
(vec).cap = new_cap; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @def vec_sized_push(vec, elem_size, src_ptr)
|
||||
* @brief 添加一个元素(从 src_ptr 拷贝 elem_size 字节)
|
||||
* @param vec VEC(type) 定义的向量变量(type 可为 char 或 void)
|
||||
* @param elem_size 每个元素占用的字节数
|
||||
* @param src_ptr 源数据的指针
|
||||
* @param copy_size 要拷贝的字节数
|
||||
*
|
||||
* @note 使用前需确保 vec.data 类型与 src_ptr 无关,内部会按字节拷贝。
|
||||
* 推荐声明时为 `VEC(char)` 或 `VEC(unsigned char)`。
|
||||
*/
|
||||
#define vec_sized_push(vec, elem_size, src_ptr, copy_size) \
|
||||
do { \
|
||||
if ((vec).size >= (vec).cap) { \
|
||||
usize new_cap = (vec).cap ? (vec).cap * 2 : 4; \
|
||||
vec_sized_realloc(vec, elem_size, new_cap); \
|
||||
} \
|
||||
char *slot = (char *)(vec).data + (vec).size * (elem_size); \
|
||||
__vec_memcpy(slot, (src_ptr), (copy_size)); \
|
||||
(vec).size++; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @def vec_sized_at_ptr(vec, elem_size, idx)
|
||||
* @brief 获取第 idx 个元素的指针(void*)
|
||||
* @return 指向元素的指针,需转换为具体类型使用
|
||||
*/
|
||||
#define vec_sized_at_ptr(vec, elem_size, idx) ((void *)((char *)(vec).data + (idx) * (elem_size)))
|
||||
|
||||
/**
|
||||
* @def vec_sized_foreach(vec, elem_size, elem_ptr_var, block)
|
||||
* @brief 遍历所有元素
|
||||
* @param elem_ptr_var 循环内的变量名(void* 类型)
|
||||
* @param block 循环体语句块
|
||||
*/
|
||||
#define vec_sized_foreach(vec, elem_size, elem_ptr_var, block) \
|
||||
do { \
|
||||
for (usize __i = 0; __i < (vec).size; ++__i) { \
|
||||
void *elem_ptr_var = vec_sized_at_ptr(vec, elem_size, __i); \
|
||||
block; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @def vec_sized_pop(vec, elem_size)
|
||||
* @brief 弹出最后一个元素(仅减小 size,不返回数据)
|
||||
*/
|
||||
#define vec_sized_pop(vec, elem_size) \
|
||||
do { \
|
||||
if ((vec).size == 0) \
|
||||
LOG_FATAL("vec_sized_pop: empty\n"); \
|
||||
(vec).size--; \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @def vec_sized_clear(vec)
|
||||
* @brief 清空向量(重置 size = 0,不释放内存)
|
||||
*/
|
||||
#define vec_sized_clear(vec) ((vec).size = 0)
|
||||
|
||||
/**
|
||||
* @def vec_sized_free(vec)
|
||||
* @brief 释放向量内存(与原始 vec_free 相同,可复用)
|
||||
* @note 注意:如果元素内部有堆资源,需在释放前自行遍历调用析构函数。
|
||||
*/
|
||||
#define vec_sized_free(vec) vec_free(vec)
|
||||
|
||||
#endif /* __CORE_VEC_H__ */
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "log.h"
|
||||
|
||||
static inline int log_snprintf(char *s, size_t n, const char *format, ...) {
|
||||
int ret;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
ret = log_vsnprintf(s, n, format, args);
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int log_default_handler(logger_t *module, log_level_t level, const char *file, int line,
|
||||
const char *func, const char *fmt, ...) {
|
||||
const char *level_str;
|
||||
int offset = 0;
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
/* clang-format off */
|
||||
switch (level) {
|
||||
case LOG_LEVEL_DEBUG: level_str = "DEBUG"; break;
|
||||
case LOG_LEVEL_INFO: level_str = "INFO "; break;
|
||||
case LOG_LEVEL_WARN: level_str = "WARN "; break;
|
||||
case LOG_LEVEL_ERROR: level_str = "ERROR"; break;
|
||||
case LOG_LEVEL_FATAL: level_str = "FATAL"; break;
|
||||
case LOG_LEVEL_TRACE: level_str = "TRACE"; break;
|
||||
default: level_str = "NOTSET"; break;
|
||||
}
|
||||
/// @note: 定义 __LOG_NO_COLOR__ 会取消颜色输出
|
||||
#ifndef __LOG_NO_COLOR__
|
||||
const char *color_code;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_DEBUG: color_code = ANSI_FG_CYAN; break;
|
||||
case LOG_LEVEL_INFO: color_code = ANSI_FG_GREEN; break;
|
||||
case LOG_LEVEL_TRACE: color_code = ANSI_FG_BLUE; break;
|
||||
case LOG_LEVEL_WARN: color_code = ANSI_FG_YELLOW; break;
|
||||
case LOG_LEVEL_ERROR: color_code = ANSI_FG_RED; break;
|
||||
case LOG_LEVEL_FATAL: color_code = ANSI_FG_RED ANSI_UNDERLINED; break;
|
||||
default: color_code = ANSI_NONE;
|
||||
}
|
||||
/* clang-format on */
|
||||
offset = log_snprintf(module->buf, sizeof(module->buf),
|
||||
ANSI_BOLD "%s[%s] %s - %s:%d in %s()" ANSI_NONE " ", color_code,
|
||||
level_str, module->name, file, line, func);
|
||||
#else
|
||||
offset = log_snprintf(module->buf, sizeof(module->buf), "[%s] %s - %s:%d in %s() ", level_str,
|
||||
module->name, file, line, func);
|
||||
#endif
|
||||
/* 然后写入用户消息(如果有) */
|
||||
if (fmt && fmt[0]) {
|
||||
log_vsnprintf(module->buf + offset, sizeof(module->buf) - offset, fmt, args);
|
||||
}
|
||||
va_end(args);
|
||||
log_puts(module->buf);
|
||||
// for clangd warning
|
||||
// clang-analyzer-deadcode.DeadStores
|
||||
(void)color_code;
|
||||
(void)level_str;
|
||||
if (level & LOG_LEVEL_FATAL) {
|
||||
log_abort();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
logger_t __default_logger_root = {
|
||||
.name = "root",
|
||||
.level = LOG_LEVEL_ALL,
|
||||
.handler = log_default_handler,
|
||||
};
|
||||
|
||||
void init_logger(logger_t *logger, const char *name) {
|
||||
logger->name = name;
|
||||
logger->handler = log_default_handler;
|
||||
log_set_level(logger, LOG_LEVEL_ALL);
|
||||
}
|
||||
|
||||
void log_set_level(logger_t *logger, int level) {
|
||||
if (logger)
|
||||
logger->level = level;
|
||||
else
|
||||
__default_logger_root.level = level;
|
||||
}
|
||||
|
||||
void log_set_handler(logger_t *logger, log_handler handler) {
|
||||
if (logger)
|
||||
logger->handler = handler;
|
||||
else
|
||||
__default_logger_root.handler = handler;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* @file log.h
|
||||
* @brief 日志系统核心模块(支持多级日志、断言和异常处理)
|
||||
*/
|
||||
|
||||
#ifndef __SCC_LOG_IMPL_H__
|
||||
#define __SCC_LOG_IMPL_H__
|
||||
|
||||
#include "color.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef __SCC_LOG_IMPL_USE_STD_IMPL__
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#define log_vsnprintf vsnprintf
|
||||
#define log_puts puts
|
||||
#define log_abort() exit(1)
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__ // GCC, Clang
|
||||
#define __scc_log_unreachable() (__builtin_unreachable())
|
||||
#elif defined _MSC_VER // MSVC
|
||||
#define __scc_log_unreachable() (__assume(false))
|
||||
#elif defined __SCC_BUILTIN_UNREACHEABLE__ // The SCC Compiler (my compiler)
|
||||
#define __scc_log_unreachable() (__scc_builtin_unreachable())
|
||||
#else
|
||||
#define __scc_log_unreachable() ((void)0)
|
||||
#endif
|
||||
|
||||
#ifndef log_vsnprintf
|
||||
#define log_vsnprintf(...)
|
||||
#warning "log_vsnprintf not defined"
|
||||
#endif
|
||||
|
||||
#ifndef log_puts
|
||||
#define log_puts(...)
|
||||
#warning "log_puts not defined"
|
||||
#endif
|
||||
|
||||
#ifndef log_abort
|
||||
#define log_abort(...)
|
||||
#warning "log_abort not defined"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 日志级别枚举
|
||||
*
|
||||
* 定义日志系统的输出级别和组合标志位
|
||||
*/
|
||||
typedef enum log_level {
|
||||
LOG_LEVEL_NOTSET = 0, ///< 未设置级别(继承默认配置)
|
||||
LOG_LEVEL_DEBUG = 1 << 0, ///< 调试信息(开发阶段详细信息)
|
||||
LOG_LEVEL_INFO = 1 << 1, ///< 常规信息(系统运行状态)
|
||||
LOG_LEVEL_WARN = 1 << 2, ///< 警告信息(潜在问题提示)
|
||||
LOG_LEVEL_ERROR = 1 << 3, ///< 错误信息(可恢复的错误)
|
||||
LOG_LEVEL_FATAL = 1 << 4, ///< 致命错误(导致程序终止的严重错误)
|
||||
LOG_LEVEL_TRACE = 1 << 5, ///< 追踪(性能追踪或者栈帧追踪)
|
||||
LOG_LEVEL_ALL = 0xFF, ///< 全级别标志(组合所有日志级别)
|
||||
} log_level_t;
|
||||
|
||||
#ifndef LOGGER_MAX_BUF_SIZE
|
||||
#define LOGGER_MAX_BUF_SIZE 512 ///< 单条日志最大缓冲区尺寸
|
||||
#endif
|
||||
|
||||
typedef struct logger logger_t;
|
||||
|
||||
typedef int (*log_handler)(logger_t *module, log_level_t level, const char *file, int line,
|
||||
const char *func, const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief 日志器实例结构体
|
||||
*
|
||||
* 每个日志器实例维护独立的配置和缓冲区
|
||||
*/
|
||||
struct logger {
|
||||
const char *name; ///< 日志器名称(用于模块区分)
|
||||
log_level_t level; ///< 当前设置的日志级别
|
||||
union {
|
||||
log_handler handler;
|
||||
void *user_handler;
|
||||
}; ///< 日志处理回调函数
|
||||
void *user_data; ///< 用户自定义数据
|
||||
char buf[LOGGER_MAX_BUF_SIZE]; ///< 格式化缓冲区
|
||||
};
|
||||
|
||||
int log_default_handler(logger_t *module, log_level_t level, const char *file, int line,
|
||||
const char *func, const char *fmt, ...);
|
||||
extern logger_t __default_logger_root;
|
||||
#ifndef LOG_DEFAULT_HANDLER
|
||||
#define LOG_DEFAULT_HANDLER &__default_logger_root
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 初始化日志实例 其余参数设置为默认值
|
||||
* @param[in] logger 日志器实例指针
|
||||
* @param[in] name 日志器名称(nullptr表示获取默认日志器名称)
|
||||
*/
|
||||
void init_logger(logger_t *logger, const char *name);
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别
|
||||
* @param[in] logger 目标日志器实例
|
||||
* @param[in] level 要设置的日志级别(可组合多个级别)
|
||||
*/
|
||||
void log_set_level(logger_t *logger, int level);
|
||||
|
||||
/**
|
||||
* @brief 设置自定义日志处理器
|
||||
* @param[in] logger 目标日志器实例
|
||||
* @param[in] handler 自定义处理函数(nullptr恢复默认处理)
|
||||
*/
|
||||
void log_set_handler(logger_t *logger, log_handler handler);
|
||||
|
||||
#ifndef LOG_MAX_MAROC_BUF_SIZE
|
||||
#define LOG_MAX_MAROC_BUF_SIZE LOGGER_MAX_BUF_SIZE ///< 宏展开缓冲区尺寸
|
||||
#endif
|
||||
|
||||
#define SCC_LOG_HANDLE_ARGS(_module_, _level_, ...) \
|
||||
(_module_), (_level_), __FILE__, __LINE__, __func__, ##__VA_ARGS__
|
||||
|
||||
#define SCC_LOG_IMPL(_module_, _level_, _fmt_, ...) \
|
||||
do { \
|
||||
/* TODO check _module_ is nullptr */ \
|
||||
if ((_module_)->handler && ((_module_)->level & (_level_))) \
|
||||
(_module_)->handler(SCC_LOG_HANDLE_ARGS(_module_, _level_, _fmt_, ##__VA_ARGS__)); \
|
||||
} while (0)
|
||||
|
||||
/* clang-format off */
|
||||
/// @name 模块日志宏
|
||||
/// @{
|
||||
#define MLOG_NOTSET(module, ...)SCC_LOG_IMPL(module, LOG_LEVEL_NOTSET, __VA_ARGS__) ///< 未分类日志
|
||||
#define MLOG_DEBUG(module, ...) SCC_LOG_IMPL(module, LOG_LEVEL_DEBUG, __VA_ARGS__) ///< 调试日志(需启用DEBUG级别)
|
||||
#define MLOG_INFO(module, ...) SCC_LOG_IMPL(module, LOG_LEVEL_INFO, __VA_ARGS__) ///< 信息日志(常规运行日志)
|
||||
#define MLOG_WARN(module, ...) SCC_LOG_IMPL(module, LOG_LEVEL_WARN, __VA_ARGS__) ///< 警告日志(潜在问题)
|
||||
#define MLOG_ERROR(module, ...) SCC_LOG_IMPL(module, LOG_LEVEL_ERROR, __VA_ARGS__) ///< 错误日志(可恢复错误)
|
||||
#define MLOG_FATAL(module, ...) SCC_LOG_IMPL(module, LOG_LEVEL_FATAL, __VA_ARGS__) ///< 致命错误日志(程序终止前)
|
||||
#define MLOG_TRACE(module, ...) SCC_LOG_IMPL(module, LOG_LEVEL_TRACE, __VA_ARGS__) ///< 追踪日志(调用栈跟踪)
|
||||
/// @}
|
||||
|
||||
/// @name 快捷日志宏
|
||||
/// @{
|
||||
#define LOG_NOTSET(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_NOTSET, __VA_ARGS__) ///< 未分类日志
|
||||
#define LOG_DEBUG(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_DEBUG, __VA_ARGS__) ///< 调试日志(需启用DEBUG级别)
|
||||
#define LOG_INFO(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_INFO, __VA_ARGS__) ///< 信息日志(常规运行日志)
|
||||
#define LOG_WARN(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_WARN, __VA_ARGS__) ///< 警告日志(潜在问题)
|
||||
#define LOG_ERROR(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_ERROR, __VA_ARGS__) ///< 错误日志(可恢复错误)
|
||||
#define LOG_FATAL(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_FATAL, __VA_ARGS__) ///< 致命错误日志(程序终止前)
|
||||
#define LOG_TRACE(...) SCC_LOG_IMPL(LOG_DEFAULT_HANDLER, LOG_LEVEL_TRACE, __VA_ARGS__) ///< 追踪日志(调用栈跟踪)
|
||||
/// @}
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
* @def _Assert
|
||||
* @brief 断言检查内部宏
|
||||
* @param cond 检查条件表达式
|
||||
* @param ... 错误信息参数(格式字符串+参数)
|
||||
*/
|
||||
#define _Assert(cond, ...) \
|
||||
((void)((cond) || (__default_logger_root.handler(SCC_LOG_HANDLE_ARGS( \
|
||||
&__default_logger_root, LOG_LEVEL_FATAL, __VA_ARGS__)), \
|
||||
log_abort(), __scc_log_unreachable(), 0)))
|
||||
|
||||
/// @name 断言工具宏
|
||||
/// @{
|
||||
#define __INNERSCC_LOG_IMPL_STR(str) #str
|
||||
#define _SCC_LOG_IMPL_STR(str) __INNERSCC_LOG_IMPL_STR(str)
|
||||
#define AssertFmt(cond, format, ...) \
|
||||
_Assert(cond, "Assertion Failure: " format, ##__VA_ARGS__) ///< 带格式的断言检查
|
||||
#define PanicFmt(format, ...) _Assert(0, "Panic: " format, ##__VA_ARGS__) ///< 立即触发致命错误
|
||||
#define Assert(cond) AssertFmt(cond, "cond is `" _SCC_LOG_IMPL_STR(cond) "`") ///< 基础断言检查
|
||||
#define Panic(...) PanicFmt(__VA_ARGS__) ///< 触发致命错误(带自定义消息)
|
||||
#define TODO() PanicFmt("TODO please implement me") ///< 标记未实现代码(触发致命错误)
|
||||
#define UNREACHABLE() PanicFmt("UNREACHABLE") ///< 触发致命错误(代码不可达)
|
||||
#define FIXME(str) PanicFmt("FIXME " _SCC_LOG_IMPL_STR(str)) ///< 提醒开发者修改代码(触发致命错误)
|
||||
/// @}
|
||||
|
||||
/**
|
||||
* @brief 静态断言(编译时)
|
||||
*
|
||||
* 利用数组大小不能为负的特性
|
||||
* 或使用 _Static_assert (C11)
|
||||
*/
|
||||
#if __STDC_VERSION__ >= 201112L
|
||||
#define StaticAssert _Static_assert
|
||||
#else
|
||||
#define StaticAssert(cond, msg) extern char __static_assertion[(cond) ? 1 : -1]
|
||||
#endif
|
||||
|
||||
#ifdef __SCC_LOG_IMPL_IMPORT_SRC__
|
||||
#include "log.c"
|
||||
#endif
|
||||
|
||||
#endif /* __SCC_LOG_IMPL_H__ */
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef __UTILS_H__
|
||||
#define __UTILS_H__
|
||||
|
||||
#define __SCC_LOG_IMPL_USE_STD_IMPL__
|
||||
#include "log.h"
|
||||
|
||||
#include "core_map.h"
|
||||
#include "core_vec.h"
|
||||
|
||||
#endif /* __UTILS_H__ */
|
||||
@@ -0,0 +1,742 @@
|
||||
/* spl_cli.c SIR VM launcher + gdb-style interactive debugger
|
||||
*
|
||||
* Usage: spl_cli [options] <file.sir> [args...]
|
||||
* -d, --debug stack-canary checks
|
||||
* -g, --debug-cli interactive debugger (REPL)
|
||||
* --entry <name> entry function (default: main)
|
||||
* --trace print every instruction
|
||||
* -h, --help show help
|
||||
*
|
||||
* spl 程序 argv:argv[0]=file.sir,argv[1..]=args。
|
||||
* REPL 配合 splc0 -g(.sir 尾部内嵌 debug 段:IR 行 + VAR 行)可显示
|
||||
* 当前 IR 节点/源行/局部变量。
|
||||
*/
|
||||
|
||||
#include "spl_mcode.h"
|
||||
#include "spl_syscall.h"
|
||||
#include "spl_vm.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ================================================================
|
||||
* 命令行解析
|
||||
* ================================================================ */
|
||||
|
||||
typedef struct {
|
||||
int debug;
|
||||
int debug_cli;
|
||||
int trace;
|
||||
int disasm;
|
||||
int profile;
|
||||
int profile_interval;
|
||||
const char *profile_out;
|
||||
int stats;
|
||||
const char *entry;
|
||||
const char *path; /* file.sir */
|
||||
const char **args; /* spl 程序参数(file.sir 之后) */
|
||||
int nargs;
|
||||
} cli_opts_t;
|
||||
|
||||
static void usage(void) {
|
||||
fprintf(stderr, "Usage: spl_cli [options] <file.sir> [args...]\n"
|
||||
" -d, --debug stack-canary checks\n"
|
||||
" -g, --debug-cli interactive debugger (REPL)\n"
|
||||
" --disasm disasm dump sir\n"
|
||||
" --entry <name> entry function (default: main)\n"
|
||||
" --trace print every instruction\n"
|
||||
" --profile[=N] native sampling profiler (every N insns, default 10000)\n"
|
||||
" --profile-out <p> profile output prefix (default build/profile)\n"
|
||||
" --stats print executed instructions / call count at exit\n"
|
||||
" -h, --help show help\n");
|
||||
}
|
||||
|
||||
/* options 可穿插;`--` 终止 option 解析(本身不作为 file/args)。
|
||||
* 第一个非 option 为 file.sir,其后为 spl args。 */
|
||||
static int parse_args(int argc, const char **argv, cli_opts_t *o) {
|
||||
memset(o, 0, sizeof *o);
|
||||
o->entry = "main";
|
||||
int i = 1;
|
||||
for (; i < argc; i++) {
|
||||
const char *a = argv[i];
|
||||
if (a[0] == '-' && a[1]) {
|
||||
if (strcmp(a, "--") == 0) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
if (strcmp(a, "-d") == 0 || strcmp(a, "--debug") == 0) {
|
||||
o->debug = 1;
|
||||
} else if (strcmp(a, "-g") == 0 || strcmp(a, "--debug-cli") == 0) {
|
||||
o->debug_cli = 1;
|
||||
} else if (strcmp(a, "--trace") == 0) {
|
||||
o->trace = 1;
|
||||
} else if (strcmp(a, "--disasm") == 0) {
|
||||
o->disasm = 1;
|
||||
} else if (strcmp(a, "--stats") == 0) {
|
||||
o->stats = 1;
|
||||
} else if (strcmp(a, "--profile") == 0) {
|
||||
o->profile = 1;
|
||||
} else if (strncmp(a, "--profile=", 10) == 0) {
|
||||
o->profile = 1;
|
||||
o->profile_interval = (int)strtol(a + 10, NULL, 10);
|
||||
} else if (strcmp(a, "--profile-out") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "spl_cli: --profile-out needs a prefix\n");
|
||||
return -1;
|
||||
}
|
||||
o->profile_out = argv[++i];
|
||||
} else if (strcmp(a, "--entry") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "spl_cli: --entry needs a name\n");
|
||||
return -1;
|
||||
}
|
||||
o->entry = argv[++i];
|
||||
} else if (strcmp(a, "-h") == 0 || strcmp(a, "--help") == 0) {
|
||||
usage();
|
||||
return -1;
|
||||
} else {
|
||||
fprintf(stderr, "spl_cli: unknown option '%s'\n", a);
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* 跳过前导 `--`(pipeline 调用会带 `--` 分隔 file 与 args) */
|
||||
while (i < argc && strcmp(argv[i], "--") == 0)
|
||||
i++;
|
||||
if (i >= argc) {
|
||||
fprintf(stderr, "spl_cli: missing <file.sir>\n");
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
o->path = argv[i++];
|
||||
while (i < argc && strcmp(argv[i], "--") == 0)
|
||||
i++;
|
||||
o->args = argv + i;
|
||||
o->nargs = argc - i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Debug map(splc0 -g 追加在 .sir 尾部的文本段)
|
||||
* IR <ip> <node_ref> <line> <kind>
|
||||
* VAR <func> <name> <fp_offset> <tid> <is_param>
|
||||
* ================================================================ */
|
||||
|
||||
typedef struct {
|
||||
usize ip;
|
||||
usize ref;
|
||||
int line;
|
||||
char kind[64];
|
||||
} dbg_irline_t;
|
||||
typedef VEC(dbg_irline_t) dbg_irline_vec_t;
|
||||
|
||||
typedef struct {
|
||||
char func[128];
|
||||
char name[128];
|
||||
usize offset;
|
||||
usize tid;
|
||||
int is_param;
|
||||
} dbg_var_t;
|
||||
typedef VEC(dbg_var_t) dbg_var_vec_t;
|
||||
|
||||
typedef struct {
|
||||
dbg_irline_vec_t irlines;
|
||||
dbg_var_vec_t vars;
|
||||
int loaded;
|
||||
} dbg_map_t;
|
||||
|
||||
static void dbg_map_load(dbg_map_t *m, const char *text) {
|
||||
memset(m, 0, sizeof *m);
|
||||
vec_init(m->irlines);
|
||||
vec_init(m->vars);
|
||||
m->loaded = 0;
|
||||
if (!text)
|
||||
return;
|
||||
const char *p = strstr(text, "SPLDBG");
|
||||
if (!p)
|
||||
return;
|
||||
p += 6;
|
||||
while (p && *p) {
|
||||
const char *nl = strchr(p, '\n');
|
||||
size_t len = nl ? (size_t)(nl - p) : strlen(p);
|
||||
char line[512];
|
||||
if (len >= sizeof line)
|
||||
len = sizeof line - 1;
|
||||
memcpy(line, p, len);
|
||||
line[len] = 0;
|
||||
if (strncmp(line, "IR ", 3) == 0) {
|
||||
dbg_irline_t ir;
|
||||
memset(&ir, 0, sizeof ir);
|
||||
sscanf(line + 3, "%zu %zu %d %63s", &ir.ip, &ir.ref, &ir.line, ir.kind);
|
||||
vec_push(m->irlines, ir);
|
||||
} else if (strncmp(line, "VAR ", 4) == 0) {
|
||||
dbg_var_t v;
|
||||
memset(&v, 0, sizeof v);
|
||||
sscanf(line + 4, "%127s %127s %zu %zu %d", v.func, v.name, &v.offset, &v.tid,
|
||||
&v.is_param);
|
||||
vec_push(m->vars, v);
|
||||
}
|
||||
if (!nl)
|
||||
break;
|
||||
p = nl + 1;
|
||||
}
|
||||
m->loaded = 1;
|
||||
}
|
||||
|
||||
/* 当前 ip 所属 IR 节点行(ip 恰好是某节点首指令) */
|
||||
static const dbg_irline_t *irline_at_exact(dbg_map_t *m, usize ip) {
|
||||
for (usize i = 0; i < m->irlines.size; i++)
|
||||
if (m->irlines.data[i].ip == ip)
|
||||
return &m->irlines.data[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* 调试器核心
|
||||
* ================================================================ */
|
||||
|
||||
typedef struct {
|
||||
spl_vm_t *vm;
|
||||
spl_prog_t *prog;
|
||||
const char *entry;
|
||||
dbg_map_t map;
|
||||
usize last_ptr; /* p 最近读到的值(x 无参重读) */
|
||||
int quit;
|
||||
} dbg_t;
|
||||
|
||||
/* ---- 位置/指令显示辅助 ---- */
|
||||
|
||||
static const char *fn_name_at(spl_prog_t *prog, usize ip) {
|
||||
for (usize i = 0; i < vec_size(prog->funcs); i++) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, i);
|
||||
if (ip >= f->address && ip < f->address + f->ninsns)
|
||||
return f->name ? f->name : "?";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
/* ip 所在函数的指令范围 [*start, *end) */
|
||||
static void cur_func_bounds(spl_prog_t *prog, usize ip, usize *start, usize *end) {
|
||||
*start = 0;
|
||||
*end = vec_size(prog->insns);
|
||||
for (usize i = 0; i < vec_size(prog->funcs); i++) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, i);
|
||||
if (ip >= f->address && ip < f->address + f->ninsns) {
|
||||
*start = f->address;
|
||||
*end = f->address + f->ninsns;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 格式化一条指令(不含行号前缀) */
|
||||
static void fmt_insn(const spl_vm_ins_t *ins, char *buf, size_t n) {
|
||||
snprintf(buf, n, "%-11s %-5s %zu", spl_vm_opcode_name(ins->opcode),
|
||||
spl_vm_type_kind_name(ins->type), ins->imm);
|
||||
}
|
||||
|
||||
static void dbg_show_location(dbg_t *d) {
|
||||
spl_vm_t *vm = d->vm;
|
||||
if (vm->ip >= vec_size(vm->prog->insns)) {
|
||||
printf(" (end of program)\n");
|
||||
return;
|
||||
}
|
||||
char ib[80];
|
||||
fmt_insn(&vec_at(vm->prog->insns, vm->ip), ib, sizeof ib);
|
||||
printf(" %s @ ip=%zu %s [sp=%zu fp=%zu cp=%zu]\n", fn_name_at(vm->prog, vm->ip), vm->ip,
|
||||
ib, vm->sp, vm->fp, vm->cp);
|
||||
const dbg_irline_t *ir = irline_at_exact(&d->map, vm->ip);
|
||||
if (ir)
|
||||
printf(" -> ir node#%zu line %d %s\n", ir->ref, ir->line, ir->kind);
|
||||
}
|
||||
|
||||
static void dbg_handle_status(dbg_t *d, int r) {
|
||||
if (r == 1) {
|
||||
printf(" program halted, exit_code=%d\n", d->vm->exit_code);
|
||||
d->quit = 1;
|
||||
} else if (r == 2) {
|
||||
printf(" breakpoint hit\n");
|
||||
} else if (r == -1) {
|
||||
printf(" VM error: %s\n", d->vm->error_msg[0] ? d->vm->error_msg : "(no detail)");
|
||||
d->quit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 变量查找(p / x 复用) ---- */
|
||||
|
||||
static dbg_var_t *dbg_find_var(dbg_t *d, const char *fn, const char *name) {
|
||||
for (usize i = 0; i < d->map.vars.size; i++) {
|
||||
dbg_var_t *v = &d->map.vars.data[i];
|
||||
if (strcmp(v->func, fn) == 0 && strcmp(v->name, name) == 0)
|
||||
return v;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* 命令
|
||||
* ================================================================ */
|
||||
|
||||
static int cmd_help(dbg_t *d, const char *args);
|
||||
static int cmd_run(dbg_t *d, const char *args);
|
||||
static int cmd_step(dbg_t *d, const char *args);
|
||||
static int cmd_next(dbg_t *d, const char *args);
|
||||
static int cmd_finish(dbg_t *d, const char *args);
|
||||
static int cmd_break(dbg_t *d, const char *args);
|
||||
static int cmd_delete(dbg_t *d, const char *args);
|
||||
static int cmd_info(dbg_t *d, const char *args);
|
||||
static int cmd_list(dbg_t *d, const char *args);
|
||||
static int cmd_backtrace(dbg_t *d, const char *args);
|
||||
static int cmd_stack(dbg_t *d, const char *args);
|
||||
static int cmd_x(dbg_t *d, const char *args);
|
||||
static int cmd_p(dbg_t *d, const char *args);
|
||||
static int cmd_quit(dbg_t *d, const char *args);
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *alias;
|
||||
const char *usage;
|
||||
const char *help;
|
||||
int (*fn)(dbg_t *, const char *);
|
||||
} dbg_cmd_t;
|
||||
|
||||
static const dbg_cmd_t cmds[] = {
|
||||
/* 会话 */
|
||||
{"help", "h", "[cmd]", "list commands / help", cmd_help},
|
||||
{"quit", "q", "", "quit debugger", cmd_quit},
|
||||
/* 执行 */
|
||||
{"run", "r", "", "run to next breakpoint or exit", cmd_run},
|
||||
{"continue", "c", "", "alias of run", cmd_run},
|
||||
{"step", "s", "", "step one instruction (into calls)", cmd_step},
|
||||
{"next", "n", "", "step over a call", cmd_next},
|
||||
{"finish", "f", "", "run until current function returns", cmd_finish},
|
||||
/* 断点 */
|
||||
{"break", "b", "<fn|ip>", "set breakpoint (no arg: list)", cmd_break},
|
||||
{"delete", "d", "", "clear all breakpoints", cmd_delete},
|
||||
/* 信息 */
|
||||
{"info", "i", "b|stack|locals|args|var", "breakpoints/stack/locals", cmd_info},
|
||||
{"list", "l", "[N]", "disassemble ip±N of current function", cmd_list},
|
||||
{"backtrace", "bt", "", "print call stack backtrace", cmd_backtrace},
|
||||
{"stack", "st", "", "dump stack words", cmd_stack},
|
||||
/* 内存/变量 */
|
||||
{"x", "x", "<var|addr|fp+N>", "read memory (no arg: last pointer)", cmd_x},
|
||||
{"print", "p", "<var>", "print local variable (needs splc0 -g)", cmd_p},
|
||||
{NULL, NULL, NULL, NULL, NULL},
|
||||
};
|
||||
|
||||
static const dbg_cmd_t *find_cmd(const char *name) {
|
||||
for (const dbg_cmd_t *c = cmds; c->name; c++)
|
||||
if (!strcmp(c->name, name) || (c->alias && !strcmp(c->alias, name)))
|
||||
return c;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int cmd_help(dbg_t *d, const char *args) {
|
||||
(void)d;
|
||||
if (*args) {
|
||||
const dbg_cmd_t *c = find_cmd(args);
|
||||
if (c) {
|
||||
printf(" %-10s %-22s %s", c->name, c->usage, c->help);
|
||||
if (c->alias)
|
||||
printf(" (alias: %s)", c->alias);
|
||||
printf("\n");
|
||||
} else {
|
||||
printf(" unknown command '%s'\n", args);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
printf("commands:\n");
|
||||
for (const dbg_cmd_t *c = cmds; c->name; c++) {
|
||||
printf(" %-10s %-22s %s", c->name, c->usage, c->help);
|
||||
if (c->alias)
|
||||
printf(" (alias: %s)", c->alias);
|
||||
printf("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 执行 ---- */
|
||||
|
||||
static int ip_is_breakpoint(dbg_t *d) {
|
||||
for (usize i = 0; i < vec_size(d->vm->breakpoints); i++)
|
||||
if (d->vm->breakpoints.data[i] == (usize)d->vm->ip)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_run(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
if (ip_is_breakpoint(d)) /* 越过当前断点指令,避免原地重停 */
|
||||
spl_vm_skip_breakpoint(d->vm);
|
||||
dbg_handle_status(d, spl_vm_run_until(d->vm, 0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_step(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
dbg_handle_status(d, spl_vm_run_once(d->vm));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_next(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
/* 执行 1 条;若进入被调(cp 增),继续执行到返回原深度 */
|
||||
usize base_cp = d->vm->cp;
|
||||
int r = spl_vm_run_once(d->vm);
|
||||
if (r)
|
||||
return dbg_handle_status(d, r), 0;
|
||||
while (d->vm->cp > base_cp) {
|
||||
r = spl_vm_run_once(d->vm);
|
||||
if (r)
|
||||
return dbg_handle_status(d, r), 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_finish(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
usize base_cp = d->vm->cp;
|
||||
while (d->vm->cp >= base_cp) {
|
||||
int r = spl_vm_run_once(d->vm);
|
||||
if (r)
|
||||
return dbg_handle_status(d, r), 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 断点 ---- */
|
||||
|
||||
static int is_all_digits(const char *s) {
|
||||
if (!*s)
|
||||
return 0;
|
||||
for (; *s; s++)
|
||||
if (!isdigit((unsigned char)*s))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cmd_break(dbg_t *d, const char *args) {
|
||||
if (!*args) {
|
||||
printf(" ip breakpoints:\n");
|
||||
for (usize i = 0; i < vec_size(d->vm->breakpoints); i++)
|
||||
printf(" %zu\n", d->vm->breakpoints.data[i]);
|
||||
printf(" fn breakpoints:\n");
|
||||
for (usize i = 0; i < vec_size(d->vm->fn_breakpoints); i++)
|
||||
printf(" %s\n", d->vm->fn_breakpoints.data[i]);
|
||||
return 0;
|
||||
}
|
||||
while (*args && isspace((unsigned char)*args))
|
||||
args++;
|
||||
if (is_all_digits(args)) {
|
||||
usize ip = (usize)strtoul(args, NULL, 10);
|
||||
spl_vm_add_breakpoint(d->vm, ip);
|
||||
printf(" breakpoint at ip=%zu\n", ip);
|
||||
} else {
|
||||
spl_vm_add_breakpoint_fn(d->vm, args);
|
||||
printf(" breakpoint at function '%s'\n", args);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_delete(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
spl_vm_clear_breakpoints(d->vm);
|
||||
printf(" breakpoints cleared\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 信息 ---- */
|
||||
|
||||
/* 列出当前函数变量:args_flag=1 列参数,local_flag=1 列局部 */
|
||||
static void list_vars(dbg_t *d, int args_flag, int local_flag) {
|
||||
if (!d->map.loaded) {
|
||||
printf(" no debug info (compile with splc0 -g)\n");
|
||||
return;
|
||||
}
|
||||
const char *fn = fn_name_at(d->prog, d->vm->ip);
|
||||
int any = 0;
|
||||
for (usize i = 0; i < d->map.vars.size; i++) {
|
||||
dbg_var_t *v = &d->map.vars.data[i];
|
||||
if (strcmp(v->func, fn) != 0)
|
||||
continue;
|
||||
if ((v->is_param && !args_flag) || (!v->is_param && !local_flag))
|
||||
continue;
|
||||
printf(" %-20s @ fp+%-5zu %s\n", v->name, v->offset, v->is_param ? "[param]" : "[local]");
|
||||
any = 1;
|
||||
}
|
||||
if (!any)
|
||||
printf(" (no matching variables in %s)\n", fn);
|
||||
}
|
||||
|
||||
static int cmd_info(dbg_t *d, const char *args) {
|
||||
while (*args && isspace((unsigned char)*args))
|
||||
args++;
|
||||
if (!strncmp(args, "b", 1) || !strncmp(args, "break", 5))
|
||||
return cmd_break(d, "");
|
||||
if (!strncmp(args, "st", 2) || !strncmp(args, "stack", 5))
|
||||
spl_vm_stackdump(d->vm, d->vm->sp);
|
||||
else if (!strncmp(args, "l", 1) || !strncmp(args, "local", 5))
|
||||
list_vars(d, 0, 1);
|
||||
else if (!strncmp(args, "a", 1) || !strncmp(args, "arg", 3))
|
||||
list_vars(d, 1, 0);
|
||||
else if (!strncmp(args, "v", 1) || !strncmp(args, "var", 3))
|
||||
list_vars(d, 1, 1);
|
||||
else
|
||||
printf(" usage: info b|stack|locals|args|var\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* l [N]:当前函数内 ip±N 反汇编 */
|
||||
static int cmd_list(dbg_t *d, const char *args) {
|
||||
usize ip = d->vm->ip;
|
||||
long half = 5;
|
||||
if (*args)
|
||||
half = strtol(args, NULL, 10);
|
||||
if (half < 0)
|
||||
half = 0;
|
||||
usize fstart, fend;
|
||||
cur_func_bounds(d->prog, ip, &fstart, &fend);
|
||||
usize lo = (ip > (usize)half) ? ip - (usize)half : fstart;
|
||||
usize hi = ip + (usize)half;
|
||||
if (hi >= fend)
|
||||
hi = fend - 1;
|
||||
if (lo < fstart)
|
||||
lo = fstart;
|
||||
for (usize i = lo; i <= hi; i++) {
|
||||
char ib[80];
|
||||
fmt_insn(&vec_at(d->prog->insns, i), ib, sizeof ib);
|
||||
printf("%s%6zu: %s", i == ip ? "=>" : " ", i, ib);
|
||||
const dbg_irline_t *ir = irline_at_exact(&d->map, i);
|
||||
if (ir)
|
||||
printf(" || node#%zu line %d %s", ir->ref, ir->line, ir->kind);
|
||||
printf("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_backtrace(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
spl_vm_backtrace(d->vm, d->vm->fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_stack(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
spl_vm_stackdump(d->vm, d->vm->sp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 内存 / 变量 ---- */
|
||||
|
||||
static int cmd_x(dbg_t *d, const char *args) {
|
||||
while (*args && isspace((unsigned char)*args))
|
||||
args++;
|
||||
unsigned long long addr = 0;
|
||||
if (*args) {
|
||||
if (strncmp(args, "fp+", 3) == 0 || strncmp(args, "sp+", 3) == 0) {
|
||||
int is_fp = args[0] == 'f';
|
||||
unsigned long long off = strtoull(args + 3, NULL, 0);
|
||||
addr = (uintptr_t)((char *)&d->vm->stacks.data[is_fp ? d->vm->fp : d->vm->sp] + off);
|
||||
printf(" (%s+%llu)\n", is_fp ? "fp" : "sp", off);
|
||||
} else if (d->map.loaded && !isdigit((unsigned char)*args)) {
|
||||
const char *fn = fn_name_at(d->prog, d->vm->ip);
|
||||
dbg_var_t *v = dbg_find_var(d, fn, args);
|
||||
if (v) {
|
||||
addr = (uintptr_t)((char *)&d->vm->stacks.data[d->vm->fp] + v->offset);
|
||||
printf(" (%s @ fp+%zu)\n", v->name, v->offset);
|
||||
} else {
|
||||
printf(" no variable '%s' in %s\n", args, fn);
|
||||
}
|
||||
} else {
|
||||
addr = strtoull(args, NULL, 0);
|
||||
}
|
||||
} else {
|
||||
addr = (unsigned long long)d->last_ptr;
|
||||
}
|
||||
if (!addr) {
|
||||
printf(" usage: x <var|addr|fp+N> (no arg: re-read last printed pointer)\n");
|
||||
return 0;
|
||||
}
|
||||
unsigned char *sbase = (unsigned char *)d->vm->stacks.data;
|
||||
usize slen = d->vm->config.max_stack_depth * sizeof(spl_vm_val_t);
|
||||
printf(" addr=%#llx stack=[%p,+%zu) in=%d\n", addr, sbase, slen,
|
||||
addr >= (uintptr_t)sbase && addr < (uintptr_t)(sbase + slen));
|
||||
unsigned char *p = (unsigned char *)(uintptr_t)addr;
|
||||
printf(" ");
|
||||
for (int i = 0; i < 32; i++)
|
||||
printf("%02x ", p[i]);
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_p(dbg_t *d, const char *args) {
|
||||
while (*args && isspace((unsigned char)*args))
|
||||
args++;
|
||||
if (!*args) {
|
||||
printf(" usage: p <varname>\n");
|
||||
return 0;
|
||||
}
|
||||
if (!d->map.loaded) {
|
||||
printf(" no debug info (compile with splc0 -g)\n");
|
||||
return 0;
|
||||
}
|
||||
const char *fn = fn_name_at(d->prog, d->vm->ip);
|
||||
dbg_var_t *v = dbg_find_var(d, fn, args);
|
||||
if (!v) {
|
||||
printf(" no variable '%s' in %s\n", args, fn);
|
||||
return 0;
|
||||
}
|
||||
spl_vm_val_t *addr = (spl_vm_val_t *)((char *)&d->vm->stacks.data[d->vm->fp] + v->offset);
|
||||
printf(" %s = %zu (0x%zx) @ fp+%zu\n", v->name, *addr, *addr, v->offset);
|
||||
d->last_ptr = (usize)*addr; /* 供 x 无参重读 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_quit(dbg_t *d, const char *args) {
|
||||
(void)args;
|
||||
d->quit = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void repl_loop(dbg_t *d) {
|
||||
char line[1024];
|
||||
while (!d->quit) {
|
||||
dbg_show_location(d);
|
||||
printf("spl-dbg> ");
|
||||
fflush(stdout);
|
||||
if (!fgets(line, sizeof line, stdin))
|
||||
break;
|
||||
char *nl = strchr(line, '\n');
|
||||
if (nl)
|
||||
*nl = 0;
|
||||
char *p = line;
|
||||
while (*p && isspace((unsigned char)*p))
|
||||
p++;
|
||||
if (!*p)
|
||||
continue;
|
||||
/* 拆命令名 + 参数 */
|
||||
char *sp = p;
|
||||
while (*sp && !isspace((unsigned char)*sp))
|
||||
sp++;
|
||||
char saved = *sp;
|
||||
*sp = 0;
|
||||
const dbg_cmd_t *c = find_cmd(p);
|
||||
*sp = saved;
|
||||
char *args = sp;
|
||||
while (*args && isspace((unsigned char)*args))
|
||||
args++;
|
||||
if (c) {
|
||||
c->fn(d, args);
|
||||
} else {
|
||||
printf(" unknown command '%s' (h for help)\n", p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
cli_opts_t o;
|
||||
if (parse_args(argc, argv, &o) != 0)
|
||||
return 1;
|
||||
|
||||
spl_prog_t prog;
|
||||
if (spl_prog_load_from_file(o.path, &prog) != 0) {
|
||||
fprintf(stderr, "spl_cli: cannot load '%s'\n", o.path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (o.disasm) {
|
||||
spl_prog_dump(&prog);
|
||||
return 0;
|
||||
}
|
||||
spl_syscall_register(&prog);
|
||||
|
||||
spl_vm_t vm;
|
||||
spl_vm_init(&vm);
|
||||
if (o.debug) {
|
||||
fprintf(stderr, "spl_cli: debug mode on\n");
|
||||
spl_vm_set_debug(&vm, 1);
|
||||
} else {
|
||||
spl_vm_set_debug(&vm, 0);
|
||||
}
|
||||
if (o.trace) {
|
||||
fprintf(stderr, "spl_cli: trace mode on\n");
|
||||
spl_vm_set_trace(&vm, 1);
|
||||
}
|
||||
if (spl_vm_load_prog(&vm, &prog) != 0) {
|
||||
fprintf(stderr, "spl_cli: failed to load prog\n");
|
||||
spl_vm_drop(&vm);
|
||||
spl_prog_drop(&prog);
|
||||
return 1;
|
||||
}
|
||||
if (o.profile) {
|
||||
usize interval = o.profile_interval > 0 ? (usize)o.profile_interval : 10000;
|
||||
spl_vm_profile_enable(&vm, interval);
|
||||
fprintf(stderr, "spl_cli: profile mode on (interval=%zu)\n", interval);
|
||||
}
|
||||
|
||||
/* spl 程序 argv:argv[0]=file.sir(程序名),argv[1..]=args */
|
||||
const char **spl_argv = (const char **)malloc(sizeof(char *) * (size_t)(o.nargs + 1));
|
||||
if (!spl_argv) {
|
||||
spl_vm_drop(&vm);
|
||||
spl_prog_drop(&prog);
|
||||
return 1;
|
||||
}
|
||||
spl_argv[0] = o.path;
|
||||
for (int i = 0; i < o.nargs; i++)
|
||||
spl_argv[i + 1] = o.args[i];
|
||||
|
||||
if (spl_vm_prepare(&vm, o.entry, o.nargs + 1, spl_argv, NULL) != 0) {
|
||||
fprintf(stderr, "spl_cli: entry point '%s' not found\n", o.entry);
|
||||
free(spl_argv);
|
||||
spl_vm_drop(&vm);
|
||||
spl_prog_drop(&prog);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
if (o.debug_cli) {
|
||||
dbg_t d;
|
||||
d.vm = &vm;
|
||||
d.prog = &prog;
|
||||
d.entry = o.entry;
|
||||
d.quit = 0;
|
||||
d.last_ptr = 0;
|
||||
dbg_map_load(&d.map, prog.debug);
|
||||
repl_loop(&d);
|
||||
vec_free(d.map.irlines);
|
||||
vec_free(d.map.vars);
|
||||
} else {
|
||||
ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 2)
|
||||
fprintf(stderr, "spl_cli: hit breakpoint (run with -g to debug)\n");
|
||||
}
|
||||
|
||||
if (o.stats) {
|
||||
fprintf(stderr, "spl_cli: stats steps=%llu calls=%llu\n", (unsigned long long)vm.steps,
|
||||
(unsigned long long)vm.ncalls);
|
||||
}
|
||||
|
||||
if (o.profile) {
|
||||
const char *prefix = o.profile_out ? o.profile_out : "build/profile";
|
||||
char folded[1024];
|
||||
char table[1024];
|
||||
snprintf(folded, sizeof folded, "%s.folded", prefix);
|
||||
snprintf(table, sizeof table, "%s.table", prefix);
|
||||
spl_vm_profile_dump_folded(&vm, folded);
|
||||
spl_vm_profile_dump_table(&vm, table);
|
||||
fprintf(stderr, "spl_cli: profile written to %s / %s\n", folded, table);
|
||||
}
|
||||
|
||||
free(spl_argv);
|
||||
spl_vm_drop(&vm);
|
||||
spl_prog_drop(&prog);
|
||||
|
||||
if (ret < 0)
|
||||
return 1;
|
||||
return (int)vm.exit_code;
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
/* spl_mcode.c - SPL VM machine code binary serialization, deserialization, and utilities
|
||||
*
|
||||
* Binary format (all metadata fields are spl_val_t = uint64_t LE):
|
||||
* [HEADER] magic(8) nfuncs(8) ninsns(8) nnatives(8) nstrs(8) ndata(8)
|
||||
* [FUNCS] each: name_len(8) name(padded to 8) idx_of_strtab(8)
|
||||
* nargs(8) ninsns(8) address(8)
|
||||
* [INSTRS] each: opcode(2) type(2) imm(8) = 12 bytes
|
||||
* [NATIVES] each: name_len(8) name(padded to 8) idx_of_strtab(8)
|
||||
* [STRTAB] each: slen(8) str(slen bytes, padded to 8)
|
||||
*/
|
||||
|
||||
#include "spl_mcode.h"
|
||||
|
||||
void spl_prog_init(spl_prog_t *prog) {
|
||||
if (!prog)
|
||||
return;
|
||||
vec_init(prog->insns);
|
||||
vec_init(prog->funcs);
|
||||
vec_init(prog->natives);
|
||||
vec_init(prog->strtab);
|
||||
vec_init(prog->gdata);
|
||||
vec_push(prog->gdata, (spl_vm_gdata_t){0});
|
||||
map_init(prog->symtab, MAP_HASH_STR, MAP_CMP_STR);
|
||||
prog->debug = NULL;
|
||||
}
|
||||
|
||||
void spl_prog_drop(spl_prog_t *prog) {
|
||||
if (!prog)
|
||||
return;
|
||||
vec_free(prog->insns);
|
||||
/* free each func name */
|
||||
vec_for(prog->funcs, i) { free(vec_at(prog->funcs, i).name); }
|
||||
vec_free(prog->funcs);
|
||||
/* free each native name */
|
||||
vec_for(prog->natives, i) { free(vec_at(prog->natives, i).name); }
|
||||
vec_free(prog->natives);
|
||||
/* free each gdata entry */
|
||||
vec_for(prog->gdata, i) { free(vec_at(prog->gdata, i).data); }
|
||||
vec_free(prog->gdata);
|
||||
/* free each strtab entry */
|
||||
vec_for(prog->strtab, i) { free((void *)vec_at(prog->strtab, i)); }
|
||||
vec_free(prog->strtab);
|
||||
map_free(prog->symtab);
|
||||
if (prog->debug) {
|
||||
free(prog->debug);
|
||||
}
|
||||
prog->debug = NULL;
|
||||
}
|
||||
|
||||
void spl_prog_dump(spl_prog_t *prog) {
|
||||
printf(";; SPL bytecode dump: \n");
|
||||
printf(";; ninsns=%zu nfuncs=%zu nnatives=%zu nstrs=%zu\n\n", vec_size(prog->insns),
|
||||
vec_size(prog->funcs), vec_size(prog->natives), vec_size(prog->strtab));
|
||||
|
||||
if (vec_size(prog->funcs) > 0) {
|
||||
printf(";; --- functions ---\n");
|
||||
for (usize i = 0; i < vec_size(prog->funcs); i++) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, i);
|
||||
printf(" %s nargs=%zu ninsns=%zu addr=%zu\n", f->name ? f->name : "(anon)", f->nargs,
|
||||
f->ninsns, f->address);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (vec_size(prog->natives) > 0) {
|
||||
printf(";; --- natives ---\n");
|
||||
for (int i = 0; i < (int)vec_size(prog->natives); i++) {
|
||||
spl_vm_native_t *n = &vec_at(prog->natives, i);
|
||||
printf(" %s\n", n->name ? n->name : "(anon)");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (vec_size(prog->strtab) > 0) {
|
||||
printf(";; --- string table ---\n");
|
||||
for (int i = 0; i < (int)vec_size(prog->strtab); i++) {
|
||||
printf(" %d: \"%s\"\n", i, vec_at(prog->strtab, i));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf(";; --- instructions ---\n");
|
||||
for (usize i = 0; i < vec_size(prog->insns); i++) {
|
||||
spl_vm_ins_t *ins = &vec_at(prog->insns, i);
|
||||
printf("%4zd: %s", i, spl_vm_opcode_name((spl_vm_opcode_t)ins->opcode));
|
||||
if (ins->type != SPL_VOID)
|
||||
printf(" %s", spl_vm_type_kind_name((spl_vm_kind_t)ins->type));
|
||||
printf(" %zd", ins->imm);
|
||||
|
||||
/* annotate jumps / calls */
|
||||
if (ins->opcode == SPL_JMP || ins->opcode == SPL_BZ || ins->opcode == SPL_BNZ) {
|
||||
spl_vm_val_t target = i + 1 + ins->imm;
|
||||
printf(" ; -> %zu", target);
|
||||
} else if (ins->opcode == SPL_CALL) {
|
||||
printf(" ; nargs=%zu, from stack", ins->imm);
|
||||
} else if (ins->opcode == SPL_CALLI) {
|
||||
printf(" ; indirect call");
|
||||
} else if (ins->opcode == SPL_GADDR) {
|
||||
printf(" ; gdata[%zu]", ins->imm);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- LE read/write helpers ---- */
|
||||
|
||||
static inline spl_vm_val_t rd64(const unsigned char **p) {
|
||||
spl_vm_val_t v = (spl_vm_val_t)(*p)[0] | ((spl_vm_val_t)(*p)[1] << 8) |
|
||||
((spl_vm_val_t)(*p)[2] << 16) | ((spl_vm_val_t)(*p)[3] << 24) |
|
||||
((spl_vm_val_t)(*p)[4] << 32) | ((spl_vm_val_t)(*p)[5] << 40) |
|
||||
((spl_vm_val_t)(*p)[6] << 48) | ((spl_vm_val_t)(*p)[7] << 56);
|
||||
*p += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline void wr64(unsigned char **p, spl_vm_val_t v) {
|
||||
*(*p)++ = (unsigned char)(v);
|
||||
*(*p)++ = (unsigned char)(v >> 8);
|
||||
*(*p)++ = (unsigned char)(v >> 16);
|
||||
*(*p)++ = (unsigned char)(v >> 24);
|
||||
*(*p)++ = (unsigned char)(v >> 32);
|
||||
*(*p)++ = (unsigned char)(v >> 40);
|
||||
*(*p)++ = (unsigned char)(v >> 48);
|
||||
*(*p)++ = (unsigned char)(v >> 56);
|
||||
}
|
||||
|
||||
/* Round n up to next multiple of 8 */
|
||||
#define ALIGN8(n) (((n) + 7) & ~7)
|
||||
|
||||
static int spl_prog_parse(const unsigned char *data, usize len, spl_prog_t *prog) {
|
||||
const unsigned char *p;
|
||||
spl_vm_val_t nfuncs, ninsns, nnatives, nstrs, ndata;
|
||||
|
||||
if (!data || !prog)
|
||||
return -1;
|
||||
if (len < 48)
|
||||
return -1;
|
||||
|
||||
p = data;
|
||||
|
||||
/* magic */
|
||||
if (p[0] != 'S' || p[1] != 'P' || p[2] != 'L' || p[3] != 'B' || p[4] != 'I' || p[5] != 'N' ||
|
||||
p[6] != '\0' || p[7] != '\0') {
|
||||
return -1;
|
||||
}
|
||||
p += 8;
|
||||
|
||||
spl_prog_init(prog);
|
||||
|
||||
/* header counts */
|
||||
nfuncs = rd64(&p);
|
||||
ninsns = rd64(&p);
|
||||
nnatives = rd64(&p);
|
||||
nstrs = rd64(&p);
|
||||
ndata = rd64(&p);
|
||||
|
||||
/* ---- function table ---- */
|
||||
for (spl_vm_val_t i = 0; i < nfuncs; i++) {
|
||||
spl_vm_func_t func = {0};
|
||||
spl_vm_val_t nlen = rd64(&p);
|
||||
usize pad = ALIGN8((usize)nlen) - (usize)nlen;
|
||||
|
||||
func.name = (char *)malloc((usize)nlen);
|
||||
if (!func.name) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(func.name, p, (usize)nlen);
|
||||
p += (usize)nlen + pad;
|
||||
|
||||
func.idx_of_strtab = rd64(&p);
|
||||
func.nargs = rd64(&p);
|
||||
func.ninsns = rd64(&p);
|
||||
func.address = rd64(&p);
|
||||
vec_push(prog->funcs, func);
|
||||
}
|
||||
|
||||
/* ---- instructions ---- */
|
||||
for (spl_vm_val_t i = 0; i < ninsns; i++) {
|
||||
if ((size_t)(p - data) + 12 > (size_t)len) {
|
||||
return -1;
|
||||
}
|
||||
spl_vm_ins_t ins;
|
||||
ins.opcode = (uint16_t)p[0] | ((uint16_t)p[1] << 8);
|
||||
ins.type = (uint16_t)p[2] | ((uint16_t)p[3] << 8);
|
||||
p += 4;
|
||||
ins.imm = rd64(&p);
|
||||
vec_push(prog->insns, ins);
|
||||
}
|
||||
|
||||
/* ---- native table ---- */
|
||||
for (spl_vm_val_t i = 0; i < nnatives; i++) {
|
||||
spl_vm_native_t nat = {0};
|
||||
spl_vm_val_t nlen = rd64(&p);
|
||||
usize pad = ALIGN8((usize)nlen) - (usize)nlen;
|
||||
|
||||
nat.name = (char *)malloc((usize)nlen);
|
||||
if (!nat.name) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(nat.name, p, (usize)nlen);
|
||||
p += (usize)nlen + pad;
|
||||
|
||||
nat.idx_of_strtab = rd64(&p);
|
||||
nat.impl_fn = NULL; /* function pointer can't be serialised */
|
||||
vec_push(prog->natives, nat);
|
||||
}
|
||||
|
||||
/* ---- string table ---- */
|
||||
for (spl_vm_val_t i = 0; i < nstrs; i++) {
|
||||
spl_vm_val_t slen = rd64(&p);
|
||||
usize pad = ALIGN8((usize)slen) - (usize)slen;
|
||||
char *s = (char *)malloc((usize)slen + 1);
|
||||
if (!s) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(s, p, (usize)slen);
|
||||
s[(usize)slen] = '\0';
|
||||
p += (usize)slen + pad;
|
||||
vec_push(prog->strtab, s);
|
||||
}
|
||||
|
||||
/* ---- global data ---- */
|
||||
for (spl_vm_val_t i = 0; i < ndata; i++) {
|
||||
spl_vm_gdata_t entry;
|
||||
entry.size = rd64(&p);
|
||||
usize pad = ALIGN8(entry.size) - entry.size;
|
||||
entry.data = (unsigned char *)malloc(entry.size);
|
||||
if (!entry.data) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(entry.data, p, entry.size);
|
||||
p += entry.size + pad;
|
||||
vec_push(prog->gdata, entry);
|
||||
}
|
||||
|
||||
/* ---- debug 段(splc0 -g 追加在文件尾部) ---- */
|
||||
prog->debug = NULL;
|
||||
prog->debug_size = 0;
|
||||
{
|
||||
ptrdiff_t used = p - data;
|
||||
if (used >= 0 && (usize)used < (usize)len) {
|
||||
size_t dlen = (size_t)len - (size_t)used;
|
||||
char *dbg = (char *)malloc(dlen + 1);
|
||||
if (dbg) {
|
||||
memcpy(dbg, data + used, dlen);
|
||||
dbg[dlen] = 0;
|
||||
prog->debug = dbg;
|
||||
prog->debug_size = dlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
|
||||
FILE *f;
|
||||
unsigned char *data;
|
||||
long len;
|
||||
int rc;
|
||||
|
||||
if (!fname || !prog)
|
||||
return -1;
|
||||
f = fopen(fname, "rb");
|
||||
if (!f)
|
||||
return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (len < 48) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
data = (unsigned char *)malloc((size_t)len);
|
||||
if (!data) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (fread(data, 1, (size_t)len, f) != (size_t)len) {
|
||||
free(data);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
rc = spl_prog_parse(data, (usize)len, prog);
|
||||
free(data);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog) {
|
||||
return spl_prog_parse((const unsigned char *)buffer, len, prog);
|
||||
}
|
||||
|
||||
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
|
||||
unsigned char *buf, *p;
|
||||
spl_vm_val_t i;
|
||||
size_t total;
|
||||
spl_vm_val_t nfuncs, ninsns, nnatives, nstrs, ndata;
|
||||
usize nlen, pad;
|
||||
|
||||
if (!fname || !prog)
|
||||
return -1;
|
||||
|
||||
nfuncs = vec_size(prog->funcs);
|
||||
ninsns = vec_size(prog->insns);
|
||||
nnatives = vec_size(prog->natives);
|
||||
nstrs = vec_size(prog->strtab);
|
||||
ndata = vec_size(prog->gdata) - 1; /* jump 0 node */
|
||||
|
||||
/* Calculate total size */
|
||||
total = 8 /* magic */
|
||||
+ 8 + 8 + 8 + 8 + 8; /* 5 counts (nfuncs+ninsns+nnatives+nstrs+ndata) */
|
||||
|
||||
/* funcs */
|
||||
for (i = 0; i < nfuncs; i++) {
|
||||
nlen = strlen(vec_at(prog->funcs, i).name) + 1; /* include null */
|
||||
total +=
|
||||
8 + ALIGN8(nlen) + 8 + 8 + 8 + 8; /* nlen + name(pad) + idx + nargs + ninsns + addr */
|
||||
}
|
||||
|
||||
/* insns */
|
||||
total += ninsns * 12; /* opcode(2) + type(2) + imm(8) */
|
||||
|
||||
/* natives */
|
||||
for (i = 0; i < nnatives; i++) {
|
||||
nlen = strlen(vec_at(prog->natives, i).name) + 1;
|
||||
total += 8 + ALIGN8(nlen) + 8; /* nlen + name(pad) + idx_of_strtab */
|
||||
}
|
||||
|
||||
/* strtab */
|
||||
for (i = 0; i < nstrs; i++) {
|
||||
nlen = strlen(vec_at(prog->strtab, i));
|
||||
total += 8 + ALIGN8(nlen); /* slen + str(pad) */
|
||||
}
|
||||
|
||||
/* gdata */
|
||||
for (i = 1; i < vec_size(prog->gdata); i++) {
|
||||
usize dsize = vec_at(prog->gdata, i).size;
|
||||
total += 8 + ALIGN8(dsize); /* dsize + data(pad) */
|
||||
}
|
||||
|
||||
buf = (unsigned char *)malloc(total);
|
||||
if (!buf)
|
||||
return -1;
|
||||
p = buf;
|
||||
|
||||
/* magic */
|
||||
memcpy(p, "SPLBIN\0\0", 8);
|
||||
p += 8;
|
||||
|
||||
/* counts */
|
||||
wr64(&p, nfuncs);
|
||||
wr64(&p, ninsns);
|
||||
wr64(&p, nnatives);
|
||||
wr64(&p, nstrs);
|
||||
wr64(&p, ndata);
|
||||
|
||||
/* ---- function table ---- */
|
||||
for (i = 0; i < nfuncs; i++) {
|
||||
const char *name = vec_at(prog->funcs, i).name;
|
||||
nlen = strlen(name) + 1;
|
||||
pad = ALIGN8(nlen) - nlen;
|
||||
wr64(&p, nlen);
|
||||
memcpy(p, name, nlen);
|
||||
p += nlen;
|
||||
memset(p, 0, pad);
|
||||
p += pad;
|
||||
wr64(&p, vec_at(prog->funcs, i).idx_of_strtab);
|
||||
wr64(&p, vec_at(prog->funcs, i).nargs);
|
||||
wr64(&p, vec_at(prog->funcs, i).ninsns);
|
||||
wr64(&p, vec_at(prog->funcs, i).address);
|
||||
}
|
||||
|
||||
/* ---- instructions ---- */
|
||||
for (i = 0; i < ninsns; i++) {
|
||||
spl_vm_ins_t *ins = &vec_at(prog->insns, i);
|
||||
*p++ = (unsigned char)(ins->opcode);
|
||||
*p++ = (unsigned char)(ins->opcode >> 8);
|
||||
*p++ = (unsigned char)(ins->type);
|
||||
*p++ = (unsigned char)(ins->type >> 8);
|
||||
wr64(&p, ins->imm);
|
||||
}
|
||||
|
||||
/* ---- native table ---- */
|
||||
for (i = 0; i < nnatives; i++) {
|
||||
const char *name = vec_at(prog->natives, i).name;
|
||||
nlen = strlen(name) + 1;
|
||||
pad = ALIGN8(nlen) - nlen;
|
||||
wr64(&p, nlen);
|
||||
memcpy(p, name, nlen);
|
||||
p += nlen;
|
||||
memset(p, 0, pad);
|
||||
p += pad;
|
||||
wr64(&p, vec_at(prog->natives, i).idx_of_strtab);
|
||||
}
|
||||
|
||||
/* ---- string table ---- */
|
||||
for (i = 0; i < nstrs; i++) {
|
||||
const char *s = vec_at(prog->strtab, i);
|
||||
nlen = strlen(s);
|
||||
pad = ALIGN8(nlen) - nlen;
|
||||
wr64(&p, nlen);
|
||||
memcpy(p, s, nlen);
|
||||
p += nlen;
|
||||
memset(p, 0, pad);
|
||||
p += pad;
|
||||
}
|
||||
|
||||
/* ---- global data ---- */
|
||||
for (i = 1; i <= ndata; i++) {
|
||||
spl_vm_gdata_t *entry = &vec_at(prog->gdata, i);
|
||||
pad = ALIGN8(entry->size) - entry->size;
|
||||
wr64(&p, entry->size);
|
||||
memcpy(p, entry->data, entry->size);
|
||||
p += entry->size;
|
||||
memset(p, 0, pad);
|
||||
p += pad;
|
||||
}
|
||||
|
||||
/* Write file */
|
||||
FILE *f = fopen(fname, "wb");
|
||||
if (!f) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
fwrite(buf, 1, total, f);
|
||||
fclose(f);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spl_prog_add_instr(spl_prog_t *prog, uint8_t opcode, uint8_t type, spl_vm_val_t imm) {
|
||||
if (!prog)
|
||||
return 0;
|
||||
spl_vm_ins_t ins = (spl_vm_ins_t){.opcode = opcode, .type = type, .imm = imm};
|
||||
vec_push(prog->insns, ins);
|
||||
return vec_size(prog->insns);
|
||||
}
|
||||
|
||||
int spl_prog_add_func(spl_prog_t *prog, spl_vm_func_t *func) {
|
||||
if (!prog || !func)
|
||||
return 0;
|
||||
vec_push(prog->funcs, *func);
|
||||
return vec_size(prog->funcs);
|
||||
}
|
||||
|
||||
int spl_prog_add_native(spl_prog_t *prog, spl_vm_native_t *native) {
|
||||
if (!prog || !native)
|
||||
return 0;
|
||||
vec_push(prog->natives, *native);
|
||||
return vec_size(prog->natives);
|
||||
}
|
||||
|
||||
int spl_prog_add_data(spl_prog_t *prog, void *ptr, usize size) {
|
||||
spl_vm_gdata_t entry;
|
||||
if (!prog)
|
||||
return 0;
|
||||
entry.data = (unsigned char *)malloc(size);
|
||||
if (!entry.data)
|
||||
return 0;
|
||||
memcpy(entry.data, ptr, size);
|
||||
entry.size = size;
|
||||
vec_push(prog->gdata, entry);
|
||||
return (int)(vec_size(prog->gdata) - 1);
|
||||
}
|
||||
|
||||
spl_vm_func_t *spl_prog_get_func(spl_prog_t *prog, const char *name) {
|
||||
if (!prog || !name)
|
||||
return NULL;
|
||||
vec_for(prog->funcs, i) {
|
||||
const char *match_name = vec_at(prog->funcs, i).name;
|
||||
if (match_name && strcmp(match_name, name) == 0) {
|
||||
return &vec_at(prog->funcs, i);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spl_vm_native_t *spl_prog_get_native(spl_prog_t *prog, const char *name) {
|
||||
if (!prog || !name)
|
||||
return NULL;
|
||||
vec_for(prog->natives, i) {
|
||||
const char *match_name = vec_at(prog->natives, i).name;
|
||||
if (match_name && strcmp(match_name, name) == 0) {
|
||||
return &vec_at(prog->natives, i);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *opcode_name[] = {
|
||||
#define X(opcode, name, argc, pop, push, desc) [opcode] = name,
|
||||
SPL_OPCODES(X)
|
||||
#undef X
|
||||
};
|
||||
const char *spl_vm_opcode_name(spl_vm_opcode_t opcode) { return opcode_name[opcode]; }
|
||||
const char *spl_vm_type_kind_name(spl_vm_kind_t type) {
|
||||
switch (type) {
|
||||
case SPL_VOID:
|
||||
return "void";
|
||||
case SPL_BOOL:
|
||||
return "bool";
|
||||
case SPL_I8:
|
||||
return "i8";
|
||||
case SPL_U8:
|
||||
return "u8";
|
||||
case SPL_I16:
|
||||
return "i16";
|
||||
case SPL_U16:
|
||||
return "u16";
|
||||
case SPL_I32:
|
||||
return "i32";
|
||||
case SPL_U32:
|
||||
return "u32";
|
||||
case SPL_I64:
|
||||
return "i64";
|
||||
case SPL_U64:
|
||||
return "u64";
|
||||
case SPL_F32:
|
||||
return "f32";
|
||||
case SPL_F64:
|
||||
return "f64";
|
||||
case SPL_USIZE:
|
||||
return "usize";
|
||||
case SPL_ISIZE:
|
||||
return "isize";
|
||||
case SPL_PTR:
|
||||
return "ptr";
|
||||
default:
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
void spl_vm_ins_dump(spl_vm_ins_t *ins, spl_vm_val_t addr) {
|
||||
printf("%4zu: %s", addr, spl_vm_opcode_name((spl_vm_opcode_t)ins->opcode));
|
||||
if (ins->type != SPL_VOID)
|
||||
printf(" %s", spl_vm_type_kind_name((spl_vm_kind_t)ins->type));
|
||||
printf(" %zd:%zx", ins->imm, ins->imm);
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/* spl_mcode.h - SPL VM Machine Code: instruction set and binary format
|
||||
*/
|
||||
|
||||
#ifndef __SPL_MCODE_H__
|
||||
#define __SPL_MCODE_H__
|
||||
|
||||
#include "include/core_map.h"
|
||||
#include "include/core_vec.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uintptr_t usize;
|
||||
typedef intptr_t isize;
|
||||
|
||||
typedef enum {
|
||||
SPL_VOID,
|
||||
SPL_BOOL,
|
||||
SPL_I8,
|
||||
SPL_U8,
|
||||
SPL_I16,
|
||||
SPL_U16,
|
||||
SPL_I32,
|
||||
SPL_U32,
|
||||
SPL_I64,
|
||||
SPL_U64,
|
||||
SPL_ISIZE,
|
||||
SPL_USIZE,
|
||||
SPL_F32,
|
||||
SPL_F64,
|
||||
SPL_PTR,
|
||||
SPL_TYPE_COUNT,
|
||||
} spl_vm_kind_t;
|
||||
|
||||
/* clang-format off */
|
||||
#define SPL_OPCODES(X) \
|
||||
/* opcode, name, argc, pop, push, desc */ \
|
||||
X(SPL_ERROR, "error", 0, 0, 0, "invalid opcode / internal error") \
|
||||
/* 栈操作 */ \
|
||||
X(SPL_PUSH, "push", 1, 0, 1, "push immediate") \
|
||||
X(SPL_DUP, "dup", 0, 0, 1, "duplicate top of stack") \
|
||||
X(SPL_DROP, "drop", 0, 1, 0, "discard top of stack") \
|
||||
X(SPL_SWAP, "swap", 0, 2, 2, "swap top two elements") \
|
||||
X(SPL_PICK, "pick", 1, 0, 1, "push copy of stack[imm] (0=TOS)") \
|
||||
X(SPL_ROT, "rot", 0, 3, 3, "rotate top three (a b c -- b c a)") \
|
||||
/* 算术 */ \
|
||||
X(SPL_ADD, "add", 0, 2, 1, "integer addition") \
|
||||
X(SPL_SUB, "sub", 0, 2, 1, "integer subtraction") \
|
||||
X(SPL_MUL, "mul", 0, 2, 1, "integer multiplication") \
|
||||
X(SPL_DIV_S, "div_s", 0, 2, 1, "signed division") \
|
||||
X(SPL_DIV_U, "div_u", 0, 2, 1, "unsigned division") \
|
||||
X(SPL_REM_S, "rem_s", 0, 2, 1, "signed remainder") \
|
||||
X(SPL_REM_U, "rem_u", 0, 2, 1, "unsigned remainder") \
|
||||
X(SPL_NEG, "neg", 0, 1, 1, "two's complement negation") \
|
||||
/* 位运算 */ \
|
||||
X(SPL_AND, "and", 0, 2, 1, "bitwise AND") \
|
||||
X(SPL_OR, "or", 0, 2, 1, "bitwise OR") \
|
||||
X(SPL_XOR, "xor", 0, 2, 1, "bitwise XOR") \
|
||||
X(SPL_NOT, "not", 0, 1, 1, "bitwise NOT") \
|
||||
X(SPL_SHL, "shl", 0, 2, 1, "left shift") \
|
||||
X(SPL_SHR_U, "shr_u", 0, 2, 1, "logical right shift (zero-fill)") \
|
||||
X(SPL_SHR_S, "shr_s", 0, 2, 1, "arithmetic right shift (sign-fill)") \
|
||||
/* 比较 */ \
|
||||
X(SPL_EQ, "eq", 0, 2, 1, "equal") \
|
||||
X(SPL_NE, "ne", 0, 2, 1, "not equal") \
|
||||
X(SPL_SLT, "slt", 0, 2, 1, "signed less than") \
|
||||
X(SPL_SLE, "sle", 0, 2, 1, "signed less or equal") \
|
||||
X(SPL_ULT, "ult", 0, 2, 1, "unsigned less than") \
|
||||
X(SPL_ULE, "ule", 0, 2, 1, "unsigned less or equal") \
|
||||
X(SPL_SGT, "sgt", 0, 2, 1, "signed greater than") \
|
||||
X(SPL_SGE, "sge", 0, 2, 1, "signed greater or equal") \
|
||||
X(SPL_UGT, "ugt", 0, 2, 1, "unsigned greater than") \
|
||||
X(SPL_UGE, "uge", 0, 2, 1, "unsigned greater or equal") \
|
||||
/* 类型转换 */ \
|
||||
X(SPL_TRUNC, "trunc", 1, 1, 1, "truncate to low imm bits") \
|
||||
X(SPL_SEXT, "sext", 1, 1, 1, "sign-extend from bit imm") \
|
||||
X(SPL_ZEXT, "zext", 1, 1, 1, "zero-extend from bit imm") \
|
||||
/* 控制流 */ \
|
||||
X(SPL_JMP, "jmp", 1, 0, 0, "unconditional jump (offset isize)") \
|
||||
X(SPL_BZ, "bz", 1, 1, 0, "pop; jump if zero (offset isize)") \
|
||||
X(SPL_BNZ, "bnz", 1, 1, 0, "pop; jump if non-zero (offset isize)") \
|
||||
X(SPL_HALT, "halt", 0, 0, 0, "stop execution") \
|
||||
/* 函数调用 */ \
|
||||
X(SPL_CALL, "call", 1, 0, 0, "call function pop function offset, call it") \
|
||||
X(SPL_CALLI, "calli", 0, 1, 0, "indirect call: pop function address, call it") \
|
||||
X(SPL_RET, "ret", 0, 0, 0, "return from function") \
|
||||
/* 栈帧局部变量 */ \
|
||||
X(SPL_ALLOC, "alloc", 1, 0, 0, "allocate imm zero-slots on stack") \
|
||||
X(SPL_LADDR, "laddr", 1, 0, 1, "push address of local at fp + imm bytes") \
|
||||
X(SPL_GADDR, "gaddr", 1, 0, 1, "push address of global at gp + imm") \
|
||||
/* 间接内存访问 */ \
|
||||
X(SPL_LOAD, "load", 0, 1, 1, "load sizeof(type)-bits zero-extended") \
|
||||
X(SPL_STORE, "store", 0, 2, 0, "store low sizeof(type)-bits") \
|
||||
/* 原生接口 */ \
|
||||
X(SPL_NCALL, "ncall", 1, 0, 1, "call native function by index") \
|
||||
X(SPL_NLIB, "nlib", 1, 0, 0, "dlopen library (name idx)") \
|
||||
/* 调试 */ \
|
||||
X(SPL_BK, "breakpoint", 0, 0, 0, "break point when exec will stop run") \
|
||||
X(SPL_DBG, "dbg", 0, 0, 0, "print VM debug info (stack, backtrace, locals)")
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
typedef enum {
|
||||
#define X(opcode, name, argc, pop, push, desc) opcode,
|
||||
SPL_OPCODES(X)
|
||||
#undef X
|
||||
SPL_OPCODE_COUNT,
|
||||
} spl_vm_opcode_t;
|
||||
|
||||
/*
|
||||
* Binary format (all metadata fields spl_val_t LE):
|
||||
* magic[8] = "SPLBIN\0\0"
|
||||
* nfuncs, ninsns, nnatives, nstrs, ndata
|
||||
* [func table] each: name_len, name(pad8), idx_of_strtab, nargs, ninsns, address
|
||||
* [insns] each: opcode(2) type(2) imm(8) = 12 bytes
|
||||
* [natives] each: name_len, name(pad8), idx_of_strtab
|
||||
* [strtab] each: slen, str(pad8)
|
||||
* [gdata] each: dsize(8), data(dsize bytes, padded to 8)
|
||||
*
|
||||
* === Aggregate Return ABI (sret) ===
|
||||
*
|
||||
* 凡返回值类型为聚合(struct / union / enum / array / shape),
|
||||
* 采用调用者分配 / 被调用者填充的间接返回约定:
|
||||
*
|
||||
* Before CALL:
|
||||
* - caller 在自己的帧里 alloca 一块 RetTy 大小的空间
|
||||
* - 该空间的地址作为第 0 个实参先压栈
|
||||
* - 其余实参照常从左到右压栈
|
||||
* - target 地址最后压栈
|
||||
* - CALL imm = 普通实参个数 + 1
|
||||
*
|
||||
* Callee 进入后:
|
||||
* - fp+0 存放 sret 指针
|
||||
* - 形参 i 位于 fp + 8*(i+1)
|
||||
* - 返回值直接写入 *(fp+0),然后执行 RET void
|
||||
*
|
||||
* Caller 侧:
|
||||
* - 调用完成后,CALL 结果槽保存 sret 指针;
|
||||
* 后续对聚合值的所有读取都通过该指针进行
|
||||
*/
|
||||
|
||||
#define SPL_BINFMT_MAGIC "SPLBIN\0\0"
|
||||
// All SIR stack values are ptr-bit unsigned integers
|
||||
typedef usize spl_vm_val_t;
|
||||
|
||||
typedef struct spl_vm_ins {
|
||||
uint8_t opcode;
|
||||
uint8_t type;
|
||||
spl_vm_val_t imm;
|
||||
} spl_vm_ins_t;
|
||||
typedef VEC(spl_vm_ins_t) spl_vm_ins_vec_t;
|
||||
|
||||
typedef struct spl_vm_func {
|
||||
char *name;
|
||||
spl_vm_val_t idx_of_strtab;
|
||||
spl_vm_val_t nargs;
|
||||
spl_vm_val_t ninsns;
|
||||
spl_vm_val_t address;
|
||||
} spl_vm_func_t;
|
||||
typedef VEC(spl_vm_func_t) spl_vm_func_vec_t;
|
||||
|
||||
/* Native function pointer type */
|
||||
struct spl_vm;
|
||||
typedef struct spl_vm spl_vm_t;
|
||||
typedef spl_vm_val_t (*spl_vm_fn_t)(spl_vm_t *vm, int nargs, spl_vm_val_t *args);
|
||||
typedef struct spl_vm_native {
|
||||
char *name;
|
||||
spl_vm_val_t idx_of_strtab;
|
||||
spl_vm_fn_t impl_fn;
|
||||
} spl_vm_native_t;
|
||||
typedef VEC(spl_vm_native_t) spl_vm_native_vec_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
usize size;
|
||||
} spl_vm_gdata_t;
|
||||
typedef VEC(spl_vm_gdata_t) spl_vm_data_t;
|
||||
typedef VEC(const char *) spl_vm_strtab_t;
|
||||
typedef MAP(const char *, const char *) spl_vm_symtab_t;
|
||||
/* Opaque handle for loaded program */
|
||||
typedef struct spl_prog {
|
||||
spl_vm_ins_vec_t insns;
|
||||
spl_vm_func_vec_t funcs;
|
||||
spl_vm_native_vec_t natives;
|
||||
spl_vm_data_t gdata;
|
||||
spl_vm_strtab_t strtab;
|
||||
spl_vm_symtab_t symtab;
|
||||
char *debug; /* 文件尾部 debug 段(splc0 -g 追加的文本),无则 NULL */
|
||||
usize debug_size;
|
||||
} spl_prog_t;
|
||||
|
||||
void spl_prog_init(spl_prog_t *prog);
|
||||
void spl_prog_drop(spl_prog_t *prog);
|
||||
void spl_prog_dump(spl_prog_t *prog);
|
||||
|
||||
|
||||
|
||||
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog);
|
||||
int spl_prog_load_from_buffer(const char *buffer, usize len, spl_prog_t *prog);
|
||||
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog);
|
||||
|
||||
int spl_prog_add_instr(spl_prog_t *prog, uint8_t opcode, uint8_t type, spl_vm_val_t imm);
|
||||
int spl_prog_add_data(spl_prog_t *prog, void *ptr, usize size);
|
||||
int spl_prog_add_func(spl_prog_t *prog, spl_vm_func_t *func);
|
||||
int spl_prog_add_native(spl_prog_t *prog, spl_vm_native_t *native);
|
||||
|
||||
spl_vm_func_t *spl_prog_get_func(spl_prog_t *prog, const char *name);
|
||||
spl_vm_native_t *spl_prog_get_native(spl_prog_t *prog, const char *name);
|
||||
|
||||
/* Opcode name lookup for debugging/dumping */
|
||||
const char *spl_vm_opcode_name(spl_vm_opcode_t opcode);
|
||||
const char *spl_vm_type_kind_name(spl_vm_kind_t type);
|
||||
|
||||
void spl_vm_ins_dump(spl_vm_ins_t *ins, spl_vm_val_t addr);
|
||||
|
||||
#endif /* __SPL_MCODE_H__ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,22 @@
|
||||
/* spl_syscall.h - built-in syscall layer for SIR VM
|
||||
*
|
||||
* Provides I/O native functions (putchar, getchar, file ops, etc.)
|
||||
* that compiled SPL programs can call via NCALL.
|
||||
*
|
||||
* spl_syscall_register() must be called AFTER spl_prog_load_from_file()
|
||||
* and BEFORE spl_vm_run_until(). It matches native declarations in the
|
||||
* loaded program against known syscall names and hooks up the C
|
||||
* implementations.
|
||||
*/
|
||||
|
||||
#ifndef __SPL_SYSCALL_H__
|
||||
#define __SPL_SYSCALL_H__
|
||||
|
||||
#include "spl_mcode.h"
|
||||
|
||||
/* Register all known built-in syscalls into prog->natives[].
|
||||
* Entries whose name matches a known syscall get their impl_fn set;
|
||||
* unmatched entries remain NULL (will error at runtime if called). */
|
||||
void spl_syscall_register(spl_prog_t *prog);
|
||||
|
||||
#endif /* __SPL_SYSCALL_H__ */
|
||||
+1498
File diff suppressed because it is too large.
Load diff
+101
@@ -0,0 +1,101 @@
|
||||
/* spl_vm.h - SIR interpreter */
|
||||
|
||||
#ifndef __SPL_VM_H__
|
||||
#define __SPL_VM_H__
|
||||
|
||||
#include "include/core_map.h"
|
||||
#include "include/core_vec.h"
|
||||
#include "spl_mcode.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define SPL_STACK_CANARY ((spl_vm_val_t)0xDEADBEEFCAFEBABEull)
|
||||
|
||||
typedef struct {
|
||||
uintptr_t saved_sp;
|
||||
uintptr_t saved_fp;
|
||||
uintptr_t saved_ip;
|
||||
spl_vm_val_t nargs;
|
||||
} spl_callframe_t;
|
||||
|
||||
typedef VEC(spl_vm_val_t) spl_stack_vec_t;
|
||||
typedef VEC(spl_callframe_t) spl_frame_vec_t;
|
||||
|
||||
typedef struct {
|
||||
MAP(const char *, usize) stacks;
|
||||
VEC(usize) self_samples;
|
||||
VEC(uint64_t) self_ns; /* 每函数 time-slice 累加(纳秒) */
|
||||
uint64_t last_ns;
|
||||
uint64_t total_ns;
|
||||
usize samples;
|
||||
} spl_prof_t;
|
||||
|
||||
typedef struct spl_vm {
|
||||
spl_stack_vec_t stacks;
|
||||
spl_frame_vec_t frames;
|
||||
uintptr_t gp; // global pointer
|
||||
uintptr_t sp; // stack pointer
|
||||
uintptr_t fp; // frame pointer
|
||||
uintptr_t cp; // call pointer
|
||||
uintptr_t ip; // instr pointer
|
||||
uint64_t steps; // 已执行指令数(供性能分析参考)
|
||||
uint64_t ncalls; // 已执行 CALL/CALLI 次数(供性能分析参考)
|
||||
int exit_code;
|
||||
int trace; /* non-zero to print each instruction */
|
||||
int debug; /* non-zero to enable canary checks */
|
||||
int debug_addr; /* non-zero to check for low-address memory access */
|
||||
int skip_bp; /* non-zero: run_once 忽略下一次断点(continue 越过当前断点指令) */
|
||||
spl_prof_t *profile; /* native 采样 profiler 状态(spl_prof_t*),NULL = 关闭 */
|
||||
usize profile_interval; /* 采样间隔(指令数) */
|
||||
VEC(usize) breakpoints; /* ip 断点(执行到该指令前暂停,run_once 返回 2) */
|
||||
VEC(char *) fn_breakpoints; /* 函数名断点(CALL/CALLI 目标函数入口暂停) */
|
||||
spl_prog_t *prog;
|
||||
char error_msg[1024];
|
||||
struct {
|
||||
uintptr_t max_stack_depth;
|
||||
uintptr_t max_call_depth;
|
||||
} config;
|
||||
} spl_vm_t;
|
||||
|
||||
/* Initialize VM with default sizes (SPL_DEFAULT_STACK_SIZE /
|
||||
* SPL_DEFAULT_CALL_DEPTH) */
|
||||
void spl_vm_init(spl_vm_t *vm);
|
||||
|
||||
/* Initialize VM with custom sizes (pass 0 to use defaults) */
|
||||
void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth);
|
||||
|
||||
/* Free dynamically allocated memory in VM */
|
||||
void spl_vm_drop(spl_vm_t *vm);
|
||||
|
||||
int spl_vm_addr_valid(uintptr_t p, usize size);
|
||||
|
||||
int spl_vm_load_prog(spl_vm_t *vm, spl_prog_t *prog);
|
||||
int spl_vm_prepare(spl_vm_t *vm, const char *entry, int argc, const char **argv, const char **envp);
|
||||
|
||||
/* Enable/disable instruction-level tracing */
|
||||
void spl_vm_set_trace(spl_vm_t *vm, int enabled);
|
||||
|
||||
/* Enable/disable debug mode (stack canary protection) */
|
||||
void spl_vm_set_debug(spl_vm_t *vm, int enabled);
|
||||
|
||||
int spl_vm_run_once(spl_vm_t *vm);
|
||||
int spl_vm_run_until(spl_vm_t *vm, size_t step);
|
||||
|
||||
/* 断点:ip 断点 / 函数名断点(CALL/CALLI 目标函数入口)。命中时 run_once 返回 2。 */
|
||||
void spl_vm_add_breakpoint(spl_vm_t *vm, usize ip);
|
||||
void spl_vm_add_breakpoint_fn(spl_vm_t *vm, const char *name);
|
||||
void spl_vm_clear_breakpoints(spl_vm_t *vm);
|
||||
/* 让 run_once 执行当前指令(忽略一次断点命中);continue 越过当前断点用 */
|
||||
void spl_vm_skip_breakpoint(spl_vm_t *vm);
|
||||
|
||||
/* ---- native 采样 profiler ----
|
||||
* 每隔 interval 条指令记录一次当前调用栈(折叠格式),用于生成火焰图。
|
||||
* 必须在 prog 加载后调用 enable;dump 输出折叠栈/函数表,再 drop 时释放。 */
|
||||
void spl_vm_profile_enable(spl_vm_t *vm, usize interval);
|
||||
void spl_vm_profile_dump_folded(spl_vm_t *vm, const char *path);
|
||||
void spl_vm_profile_dump_table(spl_vm_t *vm, const char *path);
|
||||
|
||||
void spl_vm_dump_instr(spl_vm_t *vm, spl_vm_val_t ip);
|
||||
void spl_vm_stackdump(spl_vm_t *vm, spl_vm_val_t sp);
|
||||
int spl_vm_backtrace(spl_vm_t *vm, spl_vm_val_t fp);
|
||||
|
||||
#endif /* __SPL_VM_H__ */
|
||||
@@ -0,0 +1,878 @@
|
||||
/* test_spl_vm.c – unit tests for SPL VM using acutest.h
|
||||
*
|
||||
* Constructs SIR binaries, writes to temp files, loads via
|
||||
* spl_prog_load_from_file, and runs via the spl_vm_t API.
|
||||
*/
|
||||
|
||||
#include "include/acutest.h"
|
||||
#include "spl_mcode.h"
|
||||
#include "spl_vm.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ================================================================
|
||||
* Binary-writing helpers (mirrors spl_mcode.c internal format)
|
||||
* ================================================================ */
|
||||
|
||||
/* Write spl_val_t (8 LE bytes), advance pointer */
|
||||
#define LE64(p, v) \
|
||||
do { \
|
||||
unsigned char *_p = (p); \
|
||||
spl_vm_val_t _v = (spl_vm_val_t)(v); \
|
||||
*_p++ = (unsigned char)(_v); \
|
||||
*_p++ = (unsigned char)(_v >> 8); \
|
||||
*_p++ = (unsigned char)(_v >> 16); \
|
||||
*_p++ = (unsigned char)(_v >> 24); \
|
||||
*_p++ = (unsigned char)(_v >> 32); \
|
||||
*_p++ = (unsigned char)(_v >> 40); \
|
||||
*_p++ = (unsigned char)(_v >> 48); \
|
||||
*_p++ = (unsigned char)(_v >> 56); \
|
||||
(p) = _p; \
|
||||
} while (0)
|
||||
|
||||
/* Convenience: build spl_ins_t from raw fields */
|
||||
static spl_vm_ins_t ins(uint16_t op, uint16_t type, spl_vm_val_t imm) {
|
||||
spl_vm_ins_t x;
|
||||
x.opcode = op;
|
||||
x.type = type;
|
||||
x.imm = imm;
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Build a single-function SIR binary in malloc'd memory.
|
||||
* Returns buffer that must be freed by caller. */
|
||||
static unsigned char *build_binary(const char *fname, spl_vm_val_t nargs,
|
||||
const spl_vm_ins_t *instns, int ninsns, size_t *out_len) {
|
||||
size_t nlen = strlen(fname) + 1;
|
||||
size_t npad = ((nlen + 7) / 8) * 8 - nlen;
|
||||
size_t sz = 8 + 8 + 8 + 8 + 8 + 8 /* magic + 5 counts */
|
||||
+ 8 + nlen + npad + 8 + 8 + 8 + 8 /* func entry */
|
||||
+ (size_t)ninsns * 12; /* instructions */
|
||||
unsigned char *buf = (unsigned char *)malloc(sz);
|
||||
unsigned char *p = buf;
|
||||
|
||||
memcpy(p, "SPLBIN\0\0", 8);
|
||||
p += 8; /* magic */
|
||||
LE64(p, 1); /* nfuncs */
|
||||
LE64(p, (spl_vm_val_t)ninsns); /* ninsns */
|
||||
LE64(p, 0); /* nnatives */
|
||||
LE64(p, 0); /* nstrs */
|
||||
LE64(p, 0); /* ndata */
|
||||
|
||||
/* func entry */
|
||||
LE64(p, (spl_vm_val_t)nlen); /* name_len */
|
||||
memcpy(p, fname, nlen);
|
||||
p += nlen;
|
||||
memset(p, 0, npad);
|
||||
p += npad;
|
||||
LE64(p, 0); /* idx_of_strtab */
|
||||
LE64(p, nargs);
|
||||
LE64(p, (spl_vm_val_t)ninsns);
|
||||
LE64(p, 0); /* address = 0 */
|
||||
|
||||
for (int i = 0; i < ninsns; i++) {
|
||||
*p++ = (unsigned char)(instns[i].opcode);
|
||||
*p++ = (unsigned char)(instns[i].opcode >> 8);
|
||||
*p++ = (unsigned char)(instns[i].type);
|
||||
*p++ = (unsigned char)(instns[i].type >> 8);
|
||||
LE64(p, instns[i].imm);
|
||||
}
|
||||
|
||||
*out_len = sz;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* VM test helpers (file-based prog load)
|
||||
* ================================================================ */
|
||||
|
||||
static const char *TMPFILE = "test_spl_vm_tmp.bin";
|
||||
|
||||
/* Write binary buffer to temp file */
|
||||
static int write_temp(const unsigned char *bin, size_t len) {
|
||||
FILE *f = fopen(TMPFILE, "wb");
|
||||
if (!f)
|
||||
return -1;
|
||||
fwrite(bin, 1, len, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Run a single-function program, return exit code (or -1 on failure). */
|
||||
static int run(const spl_vm_ins_t *instns, int ninsns) {
|
||||
size_t len;
|
||||
unsigned char *bin = build_binary("main", 0, instns, ninsns, &len);
|
||||
spl_prog_t prog;
|
||||
spl_vm_t vm;
|
||||
int rc = -1;
|
||||
|
||||
spl_vm_init(&vm);
|
||||
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
|
||||
if (spl_vm_load_prog(&vm, &prog) == 0) {
|
||||
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 1) /* halted normally */
|
||||
rc = (int)vm.exit_code;
|
||||
else if (ret == 0) /* still running (shouldn't happen with halt) */
|
||||
rc = -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
spl_vm_drop(&vm);
|
||||
remove(TMPFILE);
|
||||
free(bin);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Run with native functions registered. */
|
||||
static int run_with_natives(const spl_vm_ins_t *instns, int ninsns, spl_vm_native_t *natives,
|
||||
int nnatives) {
|
||||
size_t len;
|
||||
unsigned char *bin = build_binary("main", 0, instns, ninsns, &len);
|
||||
spl_prog_t prog;
|
||||
spl_vm_t vm;
|
||||
int rc = -1;
|
||||
|
||||
spl_vm_init(&vm);
|
||||
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
|
||||
for (int i = 0; i < nnatives; i++)
|
||||
spl_prog_add_native(&prog, &natives[i]);
|
||||
if (spl_vm_load_prog(&vm, &prog) == 0) {
|
||||
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 1)
|
||||
rc = (int)vm.exit_code;
|
||||
else if (ret == 0)
|
||||
rc = -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
spl_vm_drop(&vm);
|
||||
remove(TMPFILE);
|
||||
free(bin);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Native function for NCALL tests
|
||||
* ================================================================ */
|
||||
|
||||
static spl_vm_val_t native_add_impl(int nargs, spl_vm_val_t *args) {
|
||||
return (nargs >= 2) ? args[0] + args[1] : 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Stack tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_push_imm(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 2) == 42);
|
||||
}
|
||||
|
||||
void test_dup(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 99), ins(SPL_DUP, SPL_VOID, 0),
|
||||
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 198);
|
||||
}
|
||||
|
||||
void test_drop(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_PUSH, SPL_I32, 2),
|
||||
ins(SPL_DROP, SPL_VOID, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_swap(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_PUSH, SPL_I32, 2),
|
||||
ins(SPL_SWAP, SPL_VOID, 0), ins(SPL_DROP, SPL_VOID, 0),
|
||||
ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 5) == 2);
|
||||
}
|
||||
|
||||
void test_pick(void) {
|
||||
/* push 10, 20, 30, pick 1 -> copy 20, add -> 50 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
|
||||
ins(SPL_PUSH, SPL_I32, 30), ins(SPL_PICK, SPL_VOID, 1),
|
||||
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 6) == 50);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Arithmetic tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_add(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 2), ins(SPL_PUSH, SPL_I32, 3),
|
||||
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 5);
|
||||
}
|
||||
|
||||
void test_sub(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 3),
|
||||
ins(SPL_SUB, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 7);
|
||||
}
|
||||
|
||||
void test_mul(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 6), ins(SPL_PUSH, SPL_I32, 7),
|
||||
ins(SPL_MUL, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 42);
|
||||
}
|
||||
|
||||
void test_div(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 100), ins(SPL_PUSH, SPL_I32, 3),
|
||||
ins(SPL_DIV_S, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 33);
|
||||
}
|
||||
|
||||
void test_rem(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 100), ins(SPL_PUSH, SPL_I32, 3),
|
||||
ins(SPL_REM_S, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_neg(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_NEG, SPL_I32, 0),
|
||||
ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 3) == -42);
|
||||
}
|
||||
|
||||
void test_i64_arith(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I64, 42), ins(SPL_RET, SPL_I64, 0)};
|
||||
TEST_CHECK(run(p, 2) == 42);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Bitwise tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_and(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFF00), ins(SPL_PUSH, SPL_U32, 0x0FF0),
|
||||
ins(SPL_AND, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0x0F00);
|
||||
}
|
||||
|
||||
void test_or(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFF00), ins(SPL_PUSH, SPL_U32, 0x00FF),
|
||||
ins(SPL_OR, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0xFFFF);
|
||||
}
|
||||
|
||||
void test_xor(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFF), ins(SPL_PUSH, SPL_U32, 0x0FF0),
|
||||
ins(SPL_XOR, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0xF00F);
|
||||
}
|
||||
|
||||
void test_not(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFF0000), ins(SPL_NOT, SPL_U32, 0),
|
||||
ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 3) == (unsigned int)0x0000FFFF);
|
||||
}
|
||||
|
||||
void test_shl(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 1), ins(SPL_PUSH, SPL_U32, 10),
|
||||
ins(SPL_SHL, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1024);
|
||||
}
|
||||
|
||||
void test_shr(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 1024), ins(SPL_PUSH, SPL_U32, 10),
|
||||
ins(SPL_SHR_U, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_shr_s(void) {
|
||||
/* arithmetic right shift: -1024 >> 5 sign-extends */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, (spl_vm_val_t)(int32_t)-1024),
|
||||
ins(SPL_PUSH, SPL_U32, 5), ins(SPL_SHR_S, SPL_I32, 0),
|
||||
ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == -32);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Comparison tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_eq(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_PUSH, SPL_I32, 42),
|
||||
ins(SPL_EQ, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_neq(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_PUSH, SPL_I32, 99),
|
||||
ins(SPL_NE, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_lt(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
|
||||
ins(SPL_SLT, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_gt_signed(void) {
|
||||
/* -1 > 1 should be 0 (false) for signed compare */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_I32, 1),
|
||||
ins(SPL_SGT, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0);
|
||||
}
|
||||
|
||||
void test_sle(void) {
|
||||
/* -1 <= 1 -> true (1) for signed */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_I32, 1),
|
||||
ins(SPL_SLE, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_sge(void) {
|
||||
/* -1 >= 1 -> false (0) for signed */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_I32, 1),
|
||||
ins(SPL_SGE, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0);
|
||||
}
|
||||
|
||||
void test_ult(void) {
|
||||
/* 0xFFFFFFFF < 1 -> false (0) for unsigned */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 1),
|
||||
ins(SPL_ULT, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0);
|
||||
}
|
||||
|
||||
void test_ule(void) {
|
||||
/* 0xFFFFFFFF <= 0xFFFFFFFF -> true (1) */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF),
|
||||
ins(SPL_ULE, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_ugt(void) {
|
||||
/* 0xFFFFFFFF > 1 -> true (1) for unsigned */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 1),
|
||||
ins(SPL_UGT, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
void test_uge(void) {
|
||||
/* 0xFFFFFFFF >= 1 -> true (1) */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 1),
|
||||
ins(SPL_UGE, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Control flow tests (relative offset)
|
||||
* ================================================================ */
|
||||
|
||||
void test_jmp(void) {
|
||||
/* push 1, jmp +3 (skip next 2 insns), push 2 (skipped), push 3, add, ret -> 4 */
|
||||
/* jmp at ip=1, after fetch ip=2, target ip=3 (push 3) => offset = 1 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_JMP, SPL_VOID, 1),
|
||||
ins(SPL_PUSH, SPL_I32, 2), ins(SPL_PUSH, SPL_I32, 3),
|
||||
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 6) == 4);
|
||||
}
|
||||
|
||||
void test_bz_bnz(void) {
|
||||
/* push 0, bz +2 (skip to ret with 1) -> return 1 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0), ins(SPL_BZ, SPL_VOID, 2),
|
||||
ins(SPL_PUSH, SPL_I32, 99), ins(SPL_RET, SPL_I32, 0),
|
||||
ins(SPL_PUSH, SPL_I32, 1), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 6) == 1);
|
||||
|
||||
/* push 1, bnz +2 (skip to ret with 42) -> return 42 */
|
||||
spl_vm_ins_t q[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_BNZ, SPL_VOID, 2),
|
||||
ins(SPL_PUSH, SPL_I32, 99), ins(SPL_RET, SPL_I32, 0),
|
||||
ins(SPL_PUSH, SPL_I32, 42), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(q, 6) == 42);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Memory tests (ALLOC, LADDR, LD, ST)
|
||||
* ================================================================ */
|
||||
|
||||
void test_alloc_laddr_st64_ld64(void) {
|
||||
/* alloc 1 local, laddr + st64 42, laddr + ld64 back -> 42 */
|
||||
spl_vm_ins_t p[] = {
|
||||
ins(SPL_ALLOC, SPL_VOID, 1), /* 0: alloc 1 local */
|
||||
ins(SPL_LADDR, SPL_VOID, 0), /* 1: addr of local[0] */
|
||||
ins(SPL_PUSH, SPL_I64, 42), /* 2: value */
|
||||
ins(SPL_STORE, SPL_I64, 0), /* 3: store */
|
||||
ins(SPL_LADDR, SPL_VOID, 0), /* 4: addr of local[0] */
|
||||
ins(SPL_LOAD, SPL_I64, 0), /* 5: load */
|
||||
ins(SPL_RET, SPL_I64, 0) /* 6: ret */
|
||||
};
|
||||
TEST_CHECK(run(p, 7) == 42);
|
||||
}
|
||||
|
||||
void test_alloc_zeroed(void) {
|
||||
/* alloc 1 local, laddr + ld64 -> should be 0 (zero-initialized) */
|
||||
spl_vm_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 1), ins(SPL_LADDR, SPL_VOID, 0),
|
||||
ins(SPL_LOAD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
|
||||
TEST_CHECK(run(p, 4) == 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Indirect memory load/store tests (stack-based, no heap)
|
||||
* ================================================================ */
|
||||
|
||||
void test_ld_st64(void) {
|
||||
/* alloc 8 slots, st64 42, ld64 back -> 42 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
|
||||
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 42),
|
||||
ins(SPL_STORE, SPL_I64, 0), ins(SPL_LOAD, SPL_I64, 0),
|
||||
ins(SPL_RET, SPL_I64, 0)};
|
||||
TEST_CHECK(run(p, 7) == 42);
|
||||
}
|
||||
|
||||
void test_ld_st32(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
|
||||
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 0xAABBCCDD),
|
||||
ins(SPL_STORE, SPL_U32, 0), ins(SPL_LOAD, SPL_U32, 0),
|
||||
ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 7) == (int)0xAABBCCDD);
|
||||
}
|
||||
|
||||
void test_ld_st16(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
|
||||
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 0xBEEF),
|
||||
ins(SPL_STORE, SPL_U16, 0), ins(SPL_LOAD, SPL_U16, 0),
|
||||
ins(SPL_RET, SPL_U16, 0)};
|
||||
TEST_CHECK(run(p, 7) == 0xBEEF);
|
||||
}
|
||||
|
||||
void test_ld_st8(void) {
|
||||
spl_vm_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
|
||||
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 0xAB),
|
||||
ins(SPL_STORE, SPL_U8, 0), ins(SPL_LOAD, SPL_U8, 0),
|
||||
ins(SPL_RET, SPL_U8, 0)};
|
||||
TEST_CHECK(run(p, 7) == 0xAB);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Function call tests (relative offset CALL)
|
||||
* ================================================================ */
|
||||
|
||||
void test_call_add(void) {
|
||||
/*
|
||||
* add(a, b) address 0, 6 insns, 2 args
|
||||
* main() address 6, 5 insns, 0 args
|
||||
*
|
||||
* add (ip=0):
|
||||
* laddr 0 ip=0 arg0 addr
|
||||
* ld64 ip=1 load arg0
|
||||
* laddr 1 ip=2 arg1 addr
|
||||
* ld64 ip=3 load arg1
|
||||
* add i64 ip=4
|
||||
* ret i64 ip=5
|
||||
*
|
||||
* main (ip=6):
|
||||
* push 10 ip=6
|
||||
* push 20 ip=7
|
||||
* push 0 ip=8 target address = add at 0
|
||||
* call 2 ip=9 nargs=2
|
||||
* ret i64 ip=10
|
||||
*/
|
||||
spl_vm_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
|
||||
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
|
||||
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
|
||||
spl_vm_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
|
||||
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_CALL, SPL_VOID, 2),
|
||||
ins(SPL_RET, SPL_I64, 0)};
|
||||
|
||||
int nadd = (int)(sizeof(add_insns) / sizeof(add_insns[0]));
|
||||
int nmain = (int)(sizeof(main_insns) / sizeof(main_insns[0]));
|
||||
int ninsns_t = nadd + nmain;
|
||||
size_t nlen0 = strlen("add") + 1;
|
||||
size_t npad0 = ((nlen0 + 7) / 8) * 8 - nlen0;
|
||||
size_t nlen1 = strlen("main") + 1;
|
||||
size_t npad1 = ((nlen1 + 7) / 8) * 8 - nlen1;
|
||||
size_t sz = 8 + 8 + 8 + 8 + 8 + 8 /* magic + 5 counts */
|
||||
+ 8 + nlen0 + npad0 + 8 + 8 + 8 + 8 /* func 0 */
|
||||
+ 8 + nlen1 + npad1 + 8 + 8 + 8 + 8 /* func 1 */
|
||||
+ (size_t)ninsns_t * 12;
|
||||
unsigned char *bin = (unsigned char *)malloc(sz);
|
||||
unsigned char *p = bin;
|
||||
|
||||
memcpy(p, "SPLBIN\0\0", 8);
|
||||
p += 8;
|
||||
LE64(p, 2);
|
||||
LE64(p, (spl_vm_val_t)ninsns_t);
|
||||
LE64(p, 0);
|
||||
LE64(p, 0);
|
||||
LE64(p, 0);
|
||||
|
||||
/* func[0]: "add" */
|
||||
LE64(p, (spl_vm_val_t)nlen0);
|
||||
memcpy(p, "add", nlen0);
|
||||
p += nlen0;
|
||||
memset(p, 0, npad0);
|
||||
p += npad0;
|
||||
LE64(p, 0);
|
||||
LE64(p, 2);
|
||||
LE64(p, (spl_vm_val_t)nadd);
|
||||
LE64(p, 0);
|
||||
|
||||
/* func[1]: "main" */
|
||||
LE64(p, (spl_vm_val_t)nlen1);
|
||||
memcpy(p, "main", nlen1);
|
||||
p += nlen1;
|
||||
memset(p, 0, npad1);
|
||||
p += npad1;
|
||||
LE64(p, 0);
|
||||
LE64(p, 0);
|
||||
LE64(p, (spl_vm_val_t)nmain);
|
||||
LE64(p, (spl_vm_val_t)nadd); /* address */
|
||||
|
||||
for (int i = 0; i < nadd; i++) {
|
||||
*p++ = (unsigned char)(add_insns[i].opcode);
|
||||
*p++ = (unsigned char)(add_insns[i].opcode >> 8);
|
||||
*p++ = (unsigned char)(add_insns[i].type);
|
||||
*p++ = (unsigned char)(add_insns[i].type >> 8);
|
||||
LE64(p, add_insns[i].imm);
|
||||
}
|
||||
for (int i = 0; i < nmain; i++) {
|
||||
*p++ = (unsigned char)(main_insns[i].opcode);
|
||||
*p++ = (unsigned char)(main_insns[i].opcode >> 8);
|
||||
*p++ = (unsigned char)(main_insns[i].type);
|
||||
*p++ = (unsigned char)(main_insns[i].type >> 8);
|
||||
LE64(p, main_insns[i].imm);
|
||||
}
|
||||
|
||||
spl_prog_t prog;
|
||||
spl_vm_t vm;
|
||||
int rc = -1;
|
||||
|
||||
spl_vm_init(&vm);
|
||||
if (write_temp(bin, sz) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
|
||||
spl_vm_load_prog(&vm, &prog);
|
||||
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 1)
|
||||
rc = (int)vm.exit_code;
|
||||
}
|
||||
spl_vm_drop(&vm);
|
||||
}
|
||||
remove(TMPFILE);
|
||||
free(bin);
|
||||
TEST_CHECK(rc == 30);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* CALLI test
|
||||
* ================================================================ */
|
||||
|
||||
void test_calli(void) {
|
||||
/*
|
||||
* add (ip=0, 2 args):
|
||||
* laddr 0 ip=0
|
||||
* ld64 ip=1
|
||||
* laddr 1 ip=2
|
||||
* ld64 ip=3
|
||||
* add i64 ip=4
|
||||
* ret i64 ip=5
|
||||
*
|
||||
* main (ip=6, 0 args):
|
||||
* push 10 ip=6
|
||||
* push 20 ip=7
|
||||
* push 2 ip=8 (nargs)
|
||||
* push 0 ip=9 (address of add)
|
||||
* calli ip=10
|
||||
* ret i64 ip=11
|
||||
*/
|
||||
spl_vm_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
|
||||
ins(SPL_LADDR, SPL_VOID, 8), ins(SPL_LOAD, SPL_I64, 0),
|
||||
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
|
||||
spl_vm_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
|
||||
ins(SPL_PUSH, SPL_VOID, 2), ins(SPL_PUSH, SPL_VOID, 0),
|
||||
ins(SPL_CALLI, SPL_VOID, 0), ins(SPL_RET, SPL_I64, 0)};
|
||||
|
||||
int ninsns_t = 6 + 6;
|
||||
size_t nlen0 = strlen("add") + 1;
|
||||
size_t npad0 = ((nlen0 + 7) / 8) * 8 - nlen0;
|
||||
size_t nlen1 = strlen("main") + 1;
|
||||
size_t npad1 = ((nlen1 + 7) / 8) * 8 - nlen1;
|
||||
size_t sz = 8 + 8 + 8 + 8 + 8 + 8 + 8 + nlen0 + npad0 + 8 + 8 + 8 + 8 + 8 + nlen1 + npad1 + 8 +
|
||||
8 + 8 + 8 + (size_t)ninsns_t * 12;
|
||||
unsigned char *bin = (unsigned char *)malloc(sz);
|
||||
unsigned char *p = bin;
|
||||
|
||||
memcpy(p, "SPLBIN\0\0", 8);
|
||||
p += 8;
|
||||
LE64(p, 2);
|
||||
LE64(p, (spl_vm_val_t)ninsns_t);
|
||||
LE64(p, 0);
|
||||
LE64(p, 0);
|
||||
LE64(p, 0);
|
||||
|
||||
/* func[0]: add */
|
||||
LE64(p, (spl_vm_val_t)nlen0);
|
||||
memcpy(p, "add", nlen0);
|
||||
p += nlen0;
|
||||
memset(p, 0, npad0);
|
||||
p += npad0;
|
||||
LE64(p, 0);
|
||||
LE64(p, 2);
|
||||
LE64(p, 6);
|
||||
LE64(p, 0);
|
||||
|
||||
/* func[1]: main */
|
||||
LE64(p, (spl_vm_val_t)nlen1);
|
||||
memcpy(p, "main", nlen1);
|
||||
p += nlen1;
|
||||
memset(p, 0, npad1);
|
||||
p += npad1;
|
||||
LE64(p, 0);
|
||||
LE64(p, 0);
|
||||
LE64(p, 6);
|
||||
LE64(p, 6);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
*p++ = (unsigned char)(add_insns[i].opcode);
|
||||
*p++ = (unsigned char)(add_insns[i].opcode >> 8);
|
||||
*p++ = (unsigned char)(add_insns[i].type);
|
||||
*p++ = (unsigned char)(add_insns[i].type >> 8);
|
||||
LE64(p, add_insns[i].imm);
|
||||
}
|
||||
for (int i = 0; i < 6; i++) {
|
||||
*p++ = (unsigned char)(main_insns[i].opcode);
|
||||
*p++ = (unsigned char)(main_insns[i].opcode >> 8);
|
||||
*p++ = (unsigned char)(main_insns[i].type);
|
||||
*p++ = (unsigned char)(main_insns[i].type >> 8);
|
||||
LE64(p, main_insns[i].imm);
|
||||
}
|
||||
|
||||
spl_prog_t prog;
|
||||
spl_vm_t vm;
|
||||
int rc = -1;
|
||||
|
||||
spl_vm_init(&vm);
|
||||
if (write_temp(bin, sz) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
|
||||
spl_vm_load_prog(&vm, &prog);
|
||||
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 1)
|
||||
rc = (int)vm.exit_code;
|
||||
}
|
||||
spl_vm_drop(&vm);
|
||||
}
|
||||
remove(TMPFILE);
|
||||
free(bin);
|
||||
TEST_CHECK(rc == 30);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Global data (GADDR) test
|
||||
* ================================================================ */
|
||||
|
||||
void test_gaddr_ld32(void) {
|
||||
/* Load program, add global data, GADDR + LD32 to read it back */
|
||||
spl_vm_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_U32, 0),
|
||||
ins(SPL_RET, SPL_U32, 0)};
|
||||
size_t len;
|
||||
unsigned char *bin = build_binary("main", 0, p, 3, &len);
|
||||
spl_prog_t prog;
|
||||
spl_vm_t vm;
|
||||
int rc = -1;
|
||||
|
||||
spl_vm_init(&vm);
|
||||
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
|
||||
uint32_t val = 0xDEADBEEF;
|
||||
spl_prog_add_data(&prog, &val, sizeof(val));
|
||||
|
||||
spl_vm_load_prog(&vm, &prog);
|
||||
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 1)
|
||||
rc = (int)vm.exit_code;
|
||||
}
|
||||
spl_vm_drop(&vm);
|
||||
}
|
||||
remove(TMPFILE);
|
||||
free(bin);
|
||||
TEST_CHECK(rc == (int)0xDEADBEEF);
|
||||
}
|
||||
|
||||
void test_gaddr_multi(void) {
|
||||
/* Two global data entries: read second one via GADDR 1 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_U32, 0),
|
||||
ins(SPL_RET, SPL_U32, 0)};
|
||||
size_t len;
|
||||
unsigned char *bin = build_binary("main", 0, p, 3, &len);
|
||||
spl_prog_t prog;
|
||||
spl_vm_t vm;
|
||||
int rc = -1;
|
||||
|
||||
spl_vm_init(&vm);
|
||||
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
|
||||
uint32_t a = 0xAAAAAAAA, b = 0xBBBBBBBB;
|
||||
spl_prog_add_data(&prog, &a, sizeof(a));
|
||||
spl_prog_add_data(&prog, &b, sizeof(b));
|
||||
|
||||
spl_vm_load_prog(&vm, &prog);
|
||||
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
if (ret == 1)
|
||||
rc = (int)vm.exit_code;
|
||||
}
|
||||
spl_vm_drop(&vm);
|
||||
}
|
||||
remove(TMPFILE);
|
||||
free(bin);
|
||||
TEST_CHECK(rc == (int)0xBBBBBBBB);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Native call test
|
||||
* ================================================================ */
|
||||
|
||||
void test_ncall(void) {
|
||||
/* main() -> i32 { return native_add(30, 12); } */
|
||||
/* NCALL imm = nargs, native index is pushed separately */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 30), ins(SPL_PUSH, SPL_I32, 12),
|
||||
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_NCALL, SPL_I32, 2),
|
||||
ins(SPL_RET, SPL_I32, 0)};
|
||||
spl_vm_native_t nat[] = {{"native_add", 0, native_add_impl}};
|
||||
TEST_CHECK(run_with_natives(p, 5, nat, 1) == 42);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Type conversion tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_trunc(void) {
|
||||
/* push 0xABCD, trunc to 8 bits -> 0xCD */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xABCD), ins(SPL_TRUNC, SPL_U8, 8),
|
||||
ins(SPL_RET, SPL_U8, 0)};
|
||||
TEST_CHECK(run(p, 3) == 0xCD);
|
||||
}
|
||||
|
||||
void test_sext(void) {
|
||||
/* push 0x80, sext from 8 bits -> 0xFFFFFF80 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0x80), ins(SPL_SEXT, SPL_I32, 8),
|
||||
ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 3) == (int)(int8_t)(0x80));
|
||||
}
|
||||
|
||||
void test_zext(void) {
|
||||
/* push 0xFFFF, zext from 8 bits -> 0xFF */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFF), ins(SPL_ZEXT, SPL_U32, 8),
|
||||
ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 3) == 0xFF);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Extended arithmetic tests (unsigned div/rem)
|
||||
* ================================================================ */
|
||||
|
||||
void test_div_u(void) {
|
||||
/* unsigned: 100 / 3 = 33 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 100), ins(SPL_PUSH, SPL_U32, 3),
|
||||
ins(SPL_DIV_U, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 33);
|
||||
}
|
||||
|
||||
void test_rem_u(void) {
|
||||
/* unsigned: 100 % 3 = 1 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 100), ins(SPL_PUSH, SPL_U32, 3),
|
||||
ins(SPL_REM_U, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
|
||||
TEST_CHECK(run(p, 4) == 1);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Edge case tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_many_ops(void) {
|
||||
/* (1+2) * (3+4) = 21 */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_PUSH, SPL_I32, 2),
|
||||
ins(SPL_ADD, SPL_I32, 0), ins(SPL_PUSH, SPL_I32, 3),
|
||||
ins(SPL_PUSH, SPL_I32, 4), ins(SPL_ADD, SPL_I32, 0),
|
||||
ins(SPL_MUL, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
|
||||
TEST_CHECK(run(p, 8) == 21);
|
||||
}
|
||||
|
||||
void test_halt(void) {
|
||||
/* push 77, halt -> exit code 0 (HALT doesn't pop) */
|
||||
spl_vm_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 77), ins(SPL_HALT, SPL_VOID, 0)};
|
||||
TEST_CHECK(run(p, 2) == 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Bad program tests
|
||||
* ================================================================ */
|
||||
|
||||
void test_bad_magic(void) {
|
||||
unsigned char bad[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
spl_prog_t prog;
|
||||
if (write_temp(bad, 8) == 0) {
|
||||
TEST_CHECK(spl_prog_load_from_file(TMPFILE, &prog) != 0);
|
||||
remove(TMPFILE);
|
||||
}
|
||||
}
|
||||
|
||||
void test_empty(void) {
|
||||
TEST_CHECK(spl_prog_load_from_file("nonexistent_file_xyz.bin", NULL) != 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test list
|
||||
* ================================================================ */
|
||||
|
||||
TEST_LIST = {
|
||||
{"push", test_push_imm},
|
||||
{"dup", test_dup},
|
||||
{"drop", test_drop},
|
||||
{"swap", test_swap},
|
||||
{"pick", test_pick},
|
||||
{"add", test_add},
|
||||
{"sub", test_sub},
|
||||
{"mul", test_mul},
|
||||
{"div", test_div},
|
||||
{"rem", test_rem},
|
||||
{"neg", test_neg},
|
||||
{"i64_arith", test_i64_arith},
|
||||
{"and", test_and},
|
||||
{"or", test_or},
|
||||
{"xor", test_xor},
|
||||
{"not", test_not},
|
||||
{"shl", test_shl},
|
||||
{"shr", test_shr},
|
||||
{"shr_s", test_shr_s},
|
||||
{"eq", test_eq},
|
||||
{"neq", test_neq},
|
||||
{"lt", test_lt},
|
||||
{"gt_signed", test_gt_signed},
|
||||
{"sle", test_sle},
|
||||
{"sge", test_sge},
|
||||
{"ult", test_ult},
|
||||
{"ule", test_ule},
|
||||
{"ugt", test_ugt},
|
||||
{"uge", test_uge},
|
||||
{"jmp", test_jmp},
|
||||
{"bz_bnz", test_bz_bnz},
|
||||
{"alloc_laddr_ld_st", test_alloc_laddr_st64_ld64},
|
||||
{"alloc_zeroed", test_alloc_zeroed},
|
||||
{"call_add", test_call_add},
|
||||
{"calli", test_calli},
|
||||
{"gaddr_ld32", test_gaddr_ld32},
|
||||
{"gaddr_multi", test_gaddr_multi},
|
||||
{"ncall", test_ncall},
|
||||
{"div_u", test_div_u},
|
||||
{"rem_u", test_rem_u},
|
||||
{"trunc", test_trunc},
|
||||
{"sext", test_sext},
|
||||
{"zext", test_zext},
|
||||
{"ld_st8", test_ld_st8},
|
||||
{"ld_st16", test_ld_st16},
|
||||
{"ld_st32", test_ld_st32},
|
||||
{"ld_st64", test_ld_st64},
|
||||
{"many_ops", test_many_ops},
|
||||
{"halt", test_halt},
|
||||
{"bad_magic", test_bad_magic},
|
||||
{"empty", test_empty},
|
||||
{NULL, NULL},
|
||||
};
|
||||
+612
@@ -0,0 +1,612 @@
|
||||
/* ir_opt.c - IR 层优化 pass
|
||||
*
|
||||
* 通用函数内联(任意大小/形状的 callee,不再限叶子):
|
||||
* - callee 体内允许 control.call(嵌套调用)、分支/循环、多个返回、unit 返回;
|
||||
* - 参数:克隆体内 Param 的“使用处”替换为调用实参 ref;
|
||||
* - 控制流:克隆体追加到调用者末尾,内部 br/jmp/label 整体 +shift;
|
||||
* - 返回:
|
||||
* * 有返回值:在调用者追加 ret 槽(MemAlloca)与常量 1;每个 Ret 改为
|
||||
* jmp 到“MemStore(槽, val) + jmp(join)”,join 处 MemLoad(槽) 供使用者;
|
||||
* * 无返回值(unit):每个 Ret 直接 jmp 到调用点之后。
|
||||
* - 调用点改为 jmp 到克隆体入口;其使用者改指到 join 的 MemLoad(或 0)。
|
||||
* - 防环:若 callee(传递)可达 caller,则拒绝内联,保证调用图保持无环、
|
||||
* 内联过程终止(直接/间接递归都不会被内联)。
|
||||
* - 迭代到不动点(无环调用图上轮数有上界)。
|
||||
* 默认关闭(splc0 --opt 驱动),且只内联显式 `#inline` 标记的 callee。 */
|
||||
|
||||
#include "ir_opt.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
spl_ir_t *ir;
|
||||
spl_ir_func_ref_t caller;
|
||||
spl_ir_func_ref_t callee;
|
||||
spl_ir_node_ref_t shift; /* new = old + shift */
|
||||
usize nparams;
|
||||
spl_ir_node_ref_t *param_ref;
|
||||
spl_ir_node_ref_t *param_arg;
|
||||
} inline_ctx_t;
|
||||
|
||||
static spl_ir_node_t *nd(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_ref_t ref) {
|
||||
return spl_ir_node(ir, fid, ref);
|
||||
}
|
||||
|
||||
/* ---- 引用重写(克隆体旧 ref -> 新 ref) ---- */
|
||||
|
||||
static spl_ir_node_ref_t rw(inline_ctx_t *c, spl_ir_node_ref_t r) {
|
||||
if (r == 0)
|
||||
return 0;
|
||||
for (usize i = 0; i < c->nparams; i++)
|
||||
if (c->param_ref[i] == r)
|
||||
return c->param_arg[i];
|
||||
return r + c->shift;
|
||||
}
|
||||
|
||||
static void rw_vec(inline_ctx_t *c, spl_ir_node_ref_vec_t *v) {
|
||||
for (usize i = 0; i < vec_size(*v); i++)
|
||||
vec_at(*v, i) = rw(c, vec_at(*v, i));
|
||||
}
|
||||
|
||||
static void rw_node(inline_ctx_t *c, spl_ir_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
n->arith.left = rw(c, n->arith.left);
|
||||
n->arith.right = rw(c, n->arith.right);
|
||||
break;
|
||||
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
|
||||
n->cmp.a = rw(c, n->cmp.a);
|
||||
n->cmp.b = rw(c, n->cmp.b);
|
||||
break;
|
||||
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
|
||||
n->cast.val = rw(c, n->cast.val);
|
||||
break;
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
n->mem_alloc.count = rw(c, n->mem_alloc.count);
|
||||
break;
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
n->mem_global_alloc.const_node = rw(c, n->mem_global_alloc.const_node);
|
||||
break;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
n->mem_load.ptr = rw(c, n->mem_load.ptr);
|
||||
break;
|
||||
case SPL_IR_MEM_STORE:
|
||||
n->mem_store.ptr = rw(c, n->mem_store.ptr);
|
||||
n->mem_store.val = rw(c, n->mem_store.val);
|
||||
break;
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
n->mem_offset.ptr = rw(c, n->mem_offset.ptr);
|
||||
n->mem_offset.offset = rw(c, n->mem_offset.offset);
|
||||
break;
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
n->mem_field_ptr.agg = rw(c, n->mem_field_ptr.agg);
|
||||
break;
|
||||
case SPL_IR_MEM_COPY:
|
||||
n->mem_copy.dst = rw(c, n->mem_copy.dst);
|
||||
n->mem_copy.src = rw(c, n->mem_copy.src);
|
||||
n->mem_copy.size = rw(c, n->mem_copy.size);
|
||||
break;
|
||||
case SPL_IR_MEM_SET:
|
||||
n->mem_set.dst = rw(c, n->mem_set.dst);
|
||||
n->mem_set.val = rw(c, n->mem_set.val);
|
||||
n->mem_set.size = rw(c, n->mem_set.size);
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
n->ir_offsetof.field_idx = rw(c, n->ir_offsetof.field_idx);
|
||||
break;
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
rw_vec(c, &n->agg_construct.fields);
|
||||
break;
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
n->agg_extract.val = rw(c, n->agg_extract.val);
|
||||
break;
|
||||
case SPL_IR_AGG_INSERT:
|
||||
n->agg_insert.agg = rw(c, n->agg_insert.agg);
|
||||
n->agg_insert.field = rw(c, n->agg_insert.field);
|
||||
break;
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
n->control_select.cond = rw(c, n->control_select.cond);
|
||||
n->control_select.true_val = rw(c, n->control_select.true_val);
|
||||
n->control_select.false_val = rw(c, n->control_select.false_val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_BR:
|
||||
n->control_br.cond = rw(c, n->control_br.cond);
|
||||
n->control_br.true_label = rw(c, n->control_br.true_label);
|
||||
n->control_br.false_label = rw(c, n->control_br.false_label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_JMP:
|
||||
n->control_jmp.label = rw(c, n->control_jmp.label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_PARAM:
|
||||
n->control_param.idx = rw(c, n->control_param.idx);
|
||||
break;
|
||||
case SPL_IR_CONTROL_RET:
|
||||
n->control_ret.val = rw(c, n->control_ret.val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
n->control_call.func = rw(c, n->control_call.func);
|
||||
rw_vec(c, &n->control_call.params);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 克隆 ---- */
|
||||
|
||||
static void clone_ref_vec(spl_ir_node_ref_vec_t *dst, const spl_ir_node_ref_vec_t *src) {
|
||||
dst->size = 0;
|
||||
dst->cap = 0;
|
||||
dst->data = NULL;
|
||||
usize n = vec_size(*src);
|
||||
if (n == 0)
|
||||
return;
|
||||
spl_ir_node_ref_t *p = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * n);
|
||||
if (!p)
|
||||
return;
|
||||
memcpy(p, src->data, sizeof(spl_ir_node_ref_t) * n);
|
||||
dst->data = p;
|
||||
dst->size = n;
|
||||
dst->cap = n;
|
||||
}
|
||||
|
||||
static void clone_node(spl_ir_t *ir, spl_ir_func_ref_t caller, spl_ir_node_ref_t dst_ref,
|
||||
const spl_ir_node_t *src) {
|
||||
spl_ir_node_t *dst = nd(ir, caller, dst_ref);
|
||||
memcpy(dst, src, sizeof(*dst));
|
||||
dst->attrs.data = NULL;
|
||||
dst->attrs.size = 0;
|
||||
dst->attrs.cap = 0;
|
||||
if (vec_size(src->attrs) != 0)
|
||||
clone_ref_vec((spl_ir_node_ref_vec_t *)&dst->attrs,
|
||||
(const spl_ir_node_ref_vec_t *)&src->attrs);
|
||||
if (src->kind == SPL_IR_AGG_CONSTRUCT)
|
||||
clone_ref_vec(&dst->agg_construct.fields, &src->agg_construct.fields);
|
||||
else if (src->kind == SPL_IR_CONTROL_CALL)
|
||||
clone_ref_vec(&dst->control_call.params, &src->control_call.params);
|
||||
}
|
||||
|
||||
/* ---- 调用图可达性(防环) ---- */
|
||||
|
||||
static spl_ir_func_ref_t call_callee(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_t *call) {
|
||||
spl_ir_node_t *fn = nd(ir, fid, call->control_call.func);
|
||||
if (!fn || fn->kind != SPL_IR_TYPE_CONST)
|
||||
return 0;
|
||||
return fn->type_const.fn;
|
||||
}
|
||||
|
||||
static bool reaches(spl_ir_t *ir, spl_ir_func_ref_t from, spl_ir_func_ref_t target,
|
||||
unsigned char *mark) {
|
||||
if (from == 0)
|
||||
return false;
|
||||
if (from == target)
|
||||
return true;
|
||||
if (mark[from])
|
||||
return false;
|
||||
mark[from] = 1;
|
||||
spl_ir_func_t *f = spl_ir_func(ir, from);
|
||||
if (!f)
|
||||
return false;
|
||||
usize n = vec_size(f->nodes);
|
||||
for (usize i = 1; i < n; i++) {
|
||||
spl_ir_node_t *x = nd(ir, from, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_CALL) {
|
||||
spl_ir_func_ref_t c = call_callee(ir, from, x);
|
||||
if (c != 0 && reaches(ir, c, target, mark))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 从 from 的直接被调者出发是否能到达 target(用于判断 from 自身是否递归/自可达)。 */
|
||||
static bool reaches_succ(spl_ir_t *ir, spl_ir_func_ref_t from, spl_ir_func_ref_t target,
|
||||
unsigned char *mark) {
|
||||
spl_ir_func_t *f = spl_ir_func(ir, from);
|
||||
if (!f)
|
||||
return false;
|
||||
usize n = vec_size(f->nodes);
|
||||
for (usize i = 1; i < n; i++) {
|
||||
spl_ir_node_t *x = nd(ir, from, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_CALL) {
|
||||
spl_ir_func_ref_t c = call_callee(ir, from, x);
|
||||
if (c != 0 && reaches(ir, c, target, mark))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ---- 内联一个调用点 ---- */
|
||||
|
||||
typedef struct {
|
||||
spl_ir_node_ref_t clone_ref;
|
||||
spl_ir_node_ref_t val;
|
||||
} ret_rec_t;
|
||||
|
||||
static bool param_index(spl_ir_t *ir, spl_ir_func_ref_t fid, spl_ir_node_t *p, usize *out) {
|
||||
spl_ir_node_t *idx = nd(ir, fid, p->control_param.idx);
|
||||
if (!idx || idx->kind != SPL_IR_TYPE_CONST)
|
||||
return false;
|
||||
*out = idx->type_const.int_lit;
|
||||
return true;
|
||||
}
|
||||
|
||||
static spl_ir_node_ref_t find_const_src(spl_ir_t *ir, spl_ir_func_ref_t fid) {
|
||||
spl_ir_func_t *f = spl_ir_func(ir, fid);
|
||||
if (!f)
|
||||
return 0;
|
||||
for (usize i = 1; i < vec_size(f->nodes); i++) {
|
||||
spl_ir_node_t *x = nd(ir, fid, i);
|
||||
if (x && x->kind == SPL_IR_TYPE_CONST)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool try_inline(spl_ir_t *ir, spl_ir_func_ref_t caller, spl_ir_node_ref_t ci,
|
||||
spl_ir_func_ref_t callee) {
|
||||
spl_ir_func_t *cf = spl_ir_func(ir, caller);
|
||||
spl_ir_func_t *calf = spl_ir_func(ir, callee);
|
||||
if (!cf || !calf || callee == caller)
|
||||
return false;
|
||||
|
||||
spl_ir_node_t *call = nd(ir, caller, ci);
|
||||
if (!call || call->kind != SPL_IR_CONTROL_CALL)
|
||||
return false;
|
||||
|
||||
usize orig_len = vec_size(cf->nodes);
|
||||
if (ci + 1 >= orig_len)
|
||||
return false;
|
||||
|
||||
usize nargs = vec_size(call->control_call.params);
|
||||
|
||||
/* 统计参数与返回,分配动态映射。 */
|
||||
usize cnodes = vec_size(calf->nodes);
|
||||
usize in_params = 0;
|
||||
usize in_rets = 0;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_t *x = nd(ir, callee, i);
|
||||
if (!x)
|
||||
return false;
|
||||
if (x->kind == SPL_IR_CONTROL_PARAM)
|
||||
in_params++;
|
||||
else if (x->kind == SPL_IR_CONTROL_RET)
|
||||
in_rets++;
|
||||
}
|
||||
if (in_rets == 0 || in_params != nargs)
|
||||
return false;
|
||||
|
||||
inline_ctx_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
c.ir = ir;
|
||||
c.caller = caller;
|
||||
c.callee = callee;
|
||||
c.nparams = in_params;
|
||||
c.param_ref = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * (in_params ? in_params : 1));
|
||||
c.param_arg = (spl_ir_node_ref_t *)malloc(sizeof(spl_ir_node_ref_t) * (in_params ? in_params : 1));
|
||||
ret_rec_t *recs = (ret_rec_t *)malloc(sizeof(ret_rec_t) * (in_rets ? in_rets : 1));
|
||||
if (!c.param_ref || !c.param_arg || !recs) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
|
||||
spl_type_id_t ret_tid = call->control_call.tid;
|
||||
|
||||
/* 参数映射(旧 ref -> 实参 ref)。 */
|
||||
usize pi = 0;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_t *x = nd(ir, callee, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_PARAM) {
|
||||
usize idx = 0;
|
||||
if (!param_index(ir, callee, x, &idx) || idx >= nargs) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
c.param_ref[pi] = i;
|
||||
c.param_arg[pi] = vec_at(call->control_call.params, idx);
|
||||
pi++;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_val = false;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_t *x = nd(ir, callee, i);
|
||||
if (x && x->kind == SPL_IR_CONTROL_RET && x->control_ret.val != 0)
|
||||
has_val = true;
|
||||
}
|
||||
spl_ir_node_ref_t const_src = find_const_src(ir, callee);
|
||||
if (has_val && const_src == 0) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 释放调用点原 params(随后 ci 改为 jmp) */
|
||||
vec_free(call->control_call.params);
|
||||
|
||||
/* 克隆体入口前置:常量 1 + ret 槽 */
|
||||
spl_ir_node_ref_t body_entry = 0;
|
||||
spl_ir_node_ref_t slot_ref = 0;
|
||||
if (has_val) {
|
||||
spl_ir_node_ref_t c_ref = spl_ir_alloc_node(ir, caller);
|
||||
clone_node(ir, caller, c_ref, nd(ir, callee, const_src));
|
||||
spl_ir_node_t *cn = nd(ir, caller, c_ref);
|
||||
cn->kind = SPL_IR_TYPE_CONST;
|
||||
cn->type_const.int_lit = 1;
|
||||
|
||||
slot_ref = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *sn = nd(ir, caller, slot_ref);
|
||||
memset(sn, 0, sizeof(*sn));
|
||||
sn->kind = SPL_IR_MEM_ALLOCA;
|
||||
sn->mem_alloc.tid = ret_tid;
|
||||
sn->mem_alloc.count = c_ref;
|
||||
|
||||
body_entry = c_ref;
|
||||
}
|
||||
|
||||
c.shift = vec_size(cf->nodes) - 1;
|
||||
if (!has_val)
|
||||
body_entry = vec_size(cf->nodes);
|
||||
|
||||
/* 克隆体 */
|
||||
usize nrec = 0;
|
||||
for (usize i = 1; i < cnodes; i++) {
|
||||
spl_ir_node_ref_t dst_ref = spl_ir_alloc_node(ir, caller);
|
||||
if (dst_ref == 0) {
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return false;
|
||||
}
|
||||
spl_ir_node_t *src = nd(ir, callee, i);
|
||||
clone_node(ir, caller, dst_ref, src);
|
||||
rw_node(&c, nd(ir, caller, dst_ref));
|
||||
spl_ir_node_t *x = nd(ir, caller, dst_ref);
|
||||
/* 克隆进来的 Param 使用处已替换为实参,节点本身不能留在调用者里
|
||||
* 改变其参数个数;退化为无害常量。 */
|
||||
if (src->kind == SPL_IR_CONTROL_PARAM) {
|
||||
x->kind = SPL_IR_TYPE_CONST;
|
||||
x->type_const.tid = 0;
|
||||
x->type_const.int_lit = 0;
|
||||
continue;
|
||||
}
|
||||
if (x->kind == SPL_IR_CONTROL_RET) {
|
||||
recs[nrec].clone_ref = dst_ref;
|
||||
recs[nrec].val = x->control_ret.val;
|
||||
nrec++;
|
||||
x->kind = SPL_IR_CONTROL_JMP;
|
||||
x->control_jmp.label = 0; /* 稍后回填 */
|
||||
}
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t ret_new = 0;
|
||||
if (has_val) {
|
||||
spl_ir_node_ref_t join = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *jn = nd(ir, caller, join);
|
||||
memset(jn, 0, sizeof(*jn));
|
||||
jn->kind = SPL_IR_MEM_LOAD;
|
||||
jn->mem_load.tid = ret_tid;
|
||||
jn->mem_load.ptr = slot_ref;
|
||||
|
||||
/* join 之后跳回调用点的后继(原代码此处靠 fallthrough)。 */
|
||||
spl_ir_node_ref_t jc = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *jcn = nd(ir, caller, jc);
|
||||
memset(jcn, 0, sizeof(*jcn));
|
||||
jcn->kind = SPL_IR_CONTROL_JMP;
|
||||
jcn->control_jmp.label = ci + 1;
|
||||
|
||||
for (usize k = 0; k < nrec; k++) {
|
||||
spl_ir_node_ref_t st = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *stn = nd(ir, caller, st);
|
||||
memset(stn, 0, sizeof(*stn));
|
||||
stn->kind = SPL_IR_MEM_STORE;
|
||||
stn->mem_store.tid = ret_tid;
|
||||
stn->mem_store.ptr = slot_ref;
|
||||
stn->mem_store.val = recs[k].val;
|
||||
|
||||
spl_ir_node_ref_t jr = spl_ir_alloc_node(ir, caller);
|
||||
spl_ir_node_t *jrn = nd(ir, caller, jr);
|
||||
memset(jrn, 0, sizeof(*jrn));
|
||||
jrn->kind = SPL_IR_CONTROL_JMP;
|
||||
jrn->control_jmp.label = join;
|
||||
|
||||
spl_ir_node_t *rn = nd(ir, caller, recs[k].clone_ref);
|
||||
rn->control_jmp.label = st;
|
||||
}
|
||||
ret_new = join;
|
||||
} else {
|
||||
for (usize k = 0; k < nrec; k++) {
|
||||
spl_ir_node_t *rn = nd(ir, caller, recs[k].clone_ref);
|
||||
rn->control_jmp.label = ci + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 调用点 -> jmp 到克隆体入口 */
|
||||
call = nd(ir, caller, ci);
|
||||
call->kind = SPL_IR_CONTROL_JMP;
|
||||
call->control_jmp.label = body_entry;
|
||||
|
||||
/* 使用者替换:只把等于 ci 的引用改为 ret_new(不动其它 ref) */
|
||||
for (usize j = 1; j < orig_len; j++) {
|
||||
spl_ir_node_t *x = nd(ir, caller, j);
|
||||
if (!x)
|
||||
continue;
|
||||
switch (x->kind) {
|
||||
case SPL_IR_ARITH_ADD: case SPL_IR_ARITH_SUB: case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV: case SPL_IR_ARITH_REM: case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_ABS: case SPL_IR_ARITH_AND: case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR: case SPL_IR_ARITH_SHL: case SPL_IR_ARITH_SHR:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
if (x->arith.left == ci) x->arith.left = ret_new;
|
||||
if (x->arith.right == ci) x->arith.right = ret_new;
|
||||
break;
|
||||
case SPL_IR_CMP_EQ: case SPL_IR_CMP_NE: case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE: case SPL_IR_CMP_GT: case SPL_IR_CMP_GE:
|
||||
if (x->cmp.a == ci) x->cmp.a = ret_new;
|
||||
if (x->cmp.b == ci) x->cmp.b = ret_new;
|
||||
break;
|
||||
case SPL_IR_CAST_TRUNC: case SPL_IR_CAST_ZEXT: case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT: case SPL_IR_CAST_FTRUNC: case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT: case SPL_IR_CAST_INT2PTR: case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT: case SPL_IR_CASE_FLOAT2INT:
|
||||
if (x->cast.val == ci) x->cast.val = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
if (x->mem_alloc.count == ci) x->mem_alloc.count = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
if (x->mem_global_alloc.const_node == ci) x->mem_global_alloc.const_node = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
if (x->mem_load.ptr == ci) x->mem_load.ptr = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_STORE:
|
||||
if (x->mem_store.ptr == ci) x->mem_store.ptr = ret_new;
|
||||
if (x->mem_store.val == ci) x->mem_store.val = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
if (x->mem_offset.ptr == ci) x->mem_offset.ptr = ret_new;
|
||||
if (x->mem_offset.offset == ci) x->mem_offset.offset = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
if (x->mem_field_ptr.agg == ci) x->mem_field_ptr.agg = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_COPY:
|
||||
if (x->mem_copy.dst == ci) x->mem_copy.dst = ret_new;
|
||||
if (x->mem_copy.src == ci) x->mem_copy.src = ret_new;
|
||||
if (x->mem_copy.size == ci) x->mem_copy.size = ret_new;
|
||||
break;
|
||||
case SPL_IR_MEM_SET:
|
||||
if (x->mem_set.dst == ci) x->mem_set.dst = ret_new;
|
||||
if (x->mem_set.val == ci) x->mem_set.val = ret_new;
|
||||
if (x->mem_set.size == ci) x->mem_set.size = ret_new;
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
if (x->ir_offsetof.field_idx == ci) x->ir_offsetof.field_idx = ret_new;
|
||||
break;
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
for (usize k = 0; k < vec_size(x->agg_construct.fields); k++)
|
||||
if (vec_at(x->agg_construct.fields, k) == ci)
|
||||
vec_at(x->agg_construct.fields, k) = ret_new;
|
||||
break;
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
if (x->agg_extract.val == ci) x->agg_extract.val = ret_new;
|
||||
break;
|
||||
case SPL_IR_AGG_INSERT:
|
||||
if (x->agg_insert.agg == ci) x->agg_insert.agg = ret_new;
|
||||
if (x->agg_insert.field == ci) x->agg_insert.field = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
if (x->control_select.cond == ci) x->control_select.cond = ret_new;
|
||||
if (x->control_select.true_val == ci) x->control_select.true_val = ret_new;
|
||||
if (x->control_select.false_val == ci) x->control_select.false_val = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_BR:
|
||||
if (x->control_br.cond == ci) x->control_br.cond = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
if (x->control_call.func == ci) x->control_call.func = ret_new;
|
||||
for (usize k = 0; k < vec_size(x->control_call.params); k++)
|
||||
if (vec_at(x->control_call.params, k) == ci)
|
||||
vec_at(x->control_call.params, k) = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_PARAM:
|
||||
if (x->control_param.idx == ci) x->control_param.idx = ret_new;
|
||||
break;
|
||||
case SPL_IR_CONTROL_RET:
|
||||
if (x->control_ret.val == ci) x->control_ret.val = ret_new;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(c.param_ref);
|
||||
free(c.param_arg);
|
||||
free(recs);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool func_has_attr(const spl_attr_t *attr, const spl_ir_func_t *f, const char *name);
|
||||
|
||||
static int opt_function(spl_ir_t *ir, spl_ir_func_ref_t fid, const spl_attr_t *attr,
|
||||
unsigned char *mark, usize nfuncs) {
|
||||
spl_ir_func_t *f = spl_ir_func(ir, fid);
|
||||
if (!f)
|
||||
return 0;
|
||||
int done = 0;
|
||||
usize orig = vec_size(f->nodes);
|
||||
for (usize ci = 1; ci < orig; ci++) {
|
||||
spl_ir_node_t *x = nd(ir, fid, ci);
|
||||
if (!x || x->kind != SPL_IR_CONTROL_CALL)
|
||||
continue;
|
||||
spl_ir_func_ref_t callee = call_callee(ir, fid, x);
|
||||
if (callee == 0 || callee == fid || callee >= nfuncs)
|
||||
continue;
|
||||
spl_ir_func_t *calf = spl_ir_func(ir, callee);
|
||||
if (!func_has_attr(attr, calf, "inline") || func_has_attr(attr, calf, "noinline"))
|
||||
continue;
|
||||
/* extern 是占位体,绝不能内联。 */
|
||||
if (func_has_attr(attr, calf, "extern"))
|
||||
continue;
|
||||
/* 防环:callee 传递可达 caller 时拒绝(覆盖直接/间接互递归)。 */
|
||||
memset(mark, 0, nfuncs);
|
||||
if (reaches(ir, callee, fid, mark))
|
||||
continue;
|
||||
/* 防爆炸:callee 自身递归时拒绝(否则不动点会反复内联其内部调用)。 */
|
||||
memset(mark, 0, nfuncs);
|
||||
if (reaches_succ(ir, callee, callee, mark))
|
||||
continue;
|
||||
if (try_inline(ir, fid, ci, callee))
|
||||
done++;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
/* 是否带有指定名字的属性/tag。 */
|
||||
static bool func_has_attr(const spl_attr_t *attr, const spl_ir_func_t *f, const char *name) {
|
||||
if (!attr || !f)
|
||||
return false;
|
||||
for (usize i = 0; i < vec_size(f->attrs); ++i) {
|
||||
spl_attr_node_t *a = spl_attr_node((spl_attr_t *)attr, vec_at(f->attrs, i));
|
||||
if (!a || !a->info)
|
||||
continue;
|
||||
if (strcmp(a->info->name, name) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void spl_ir_opt(spl_ir_t *ir, const spl_attr_t *attr) {
|
||||
if (!ir)
|
||||
return;
|
||||
usize nfuncs = vec_size(ir->funcs);
|
||||
unsigned char *mark = (unsigned char *)malloc(nfuncs ? nfuncs : 1);
|
||||
if (!mark)
|
||||
return;
|
||||
/* 无环调用图上,最多 nfuncs 轮即可到不动点。 */
|
||||
for (usize pass = 0; pass <= nfuncs; pass++) {
|
||||
int total = 0;
|
||||
usize n = vec_size(ir->funcs);
|
||||
for (spl_ir_func_ref_t fid = 1; fid < n && fid < nfuncs; fid++)
|
||||
total += opt_function(ir, fid, attr, mark, nfuncs);
|
||||
if (total == 0)
|
||||
break;
|
||||
}
|
||||
free(mark);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef __IR_OPT_H__
|
||||
#define __IR_OPT_H__
|
||||
|
||||
/* IR 层优化 pass:输入输出都是 spl_ir_t(节点 arena),纯函数式、可开关。
|
||||
* 由 splc0 --opt 在 ast2ir 之后、ir2vm 之前调用。 */
|
||||
|
||||
#include "spl_ir.h"
|
||||
|
||||
void spl_ir_opt(spl_ir_t *ir, const spl_attr_t *attr);
|
||||
|
||||
#endif /* __IR_OPT_H__ */
|
||||
@@ -0,0 +1,432 @@
|
||||
#include "spl_ast.h"
|
||||
|
||||
const char *spl_ast_kind_name(spl_ast_node_kind_t kind) {
|
||||
static const char *const names[] = {
|
||||
#define X(a, b, c) #c,
|
||||
SPL_AST_KIND_TABLE
|
||||
#undef X
|
||||
};
|
||||
if ((usize)kind < sizeof(names) / sizeof(names[0]))
|
||||
return names[kind];
|
||||
return "?";
|
||||
}
|
||||
|
||||
const char *spl_binary_op_name(spl_binary_op_t op) {
|
||||
static const char *const names[] = {
|
||||
#define X(a, b, c, name) #name,
|
||||
SPL_BINARY_OP_TABLE
|
||||
#undef X
|
||||
};
|
||||
if ((usize)op < sizeof(names) / sizeof(names[0]))
|
||||
return names[op];
|
||||
return "?";
|
||||
}
|
||||
|
||||
const char *spl_unary_op_name(spl_unary_op_t op) {
|
||||
switch (op) {
|
||||
case SPL_UNARY_OP_MINUS:
|
||||
return "neg";
|
||||
case SPL_UNARY_OP_NOT:
|
||||
return "not";
|
||||
case SPL_UNARY_OP_BIT_NOT:
|
||||
return "bitnot";
|
||||
case SPL_UNARY_OP_ADDRESS:
|
||||
return "addr";
|
||||
case SPL_UNARY_OP_DEREF:
|
||||
return "deref";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
void spl_ast_init(spl_ast_t *ast, const spl_tok_vec_t *tok_vec) {
|
||||
ast->input = *tok_vec;
|
||||
vec_init(ast->node_buckets);
|
||||
vec_init(ast->str_buckets);
|
||||
ast->root = 0;
|
||||
|
||||
spl_ast_node_t n = {0};
|
||||
vec_push(ast->node_buckets, n);
|
||||
}
|
||||
|
||||
void spl_ast_drop(spl_ast_t *ast) {
|
||||
if (!ast)
|
||||
return;
|
||||
vec_for(ast->str_buckets, idx) { free(vec_at(ast->str_buckets, idx)); }
|
||||
vec_free(ast->str_buckets);
|
||||
vec_for(ast->node_buckets, idx) { vec_free(vec_at(ast->node_buckets, idx).attrs); }
|
||||
vec_free(ast->node_buckets);
|
||||
vec_free(ast->input);
|
||||
ast->root = 0;
|
||||
}
|
||||
|
||||
spl_ast_node_t *spl_ast_node(spl_ast_t *ast, spl_ast_node_ref_t node) {
|
||||
if (!ast || node == 0 || node >= ast->node_buckets.size)
|
||||
return NULL;
|
||||
return &ast->node_buckets.data[node];
|
||||
}
|
||||
|
||||
typedef VEC(char) dump_stack_t;
|
||||
|
||||
static void dump_node(spl_ast_t *ast, spl_ast_node_ref_t node_ref, dump_stack_t *stack, bool last);
|
||||
|
||||
void spl_ast_dump(spl_ast_t *ast, spl_ast_node_ref_t node) {
|
||||
if (!ast || !node || node >= ast->node_buckets.size)
|
||||
return;
|
||||
dump_stack_t stack;
|
||||
vec_init(stack);
|
||||
printf("AST:\n");
|
||||
dump_node(ast, node, &stack, true);
|
||||
vec_free(stack);
|
||||
}
|
||||
|
||||
#define dump_vec(_ast, _vec, _stack) \
|
||||
do { \
|
||||
vec_for(_vec, _i) { dump_node(_ast, vec_at(_vec, _i), _stack, _i + 1 == vec_size(_vec)); } \
|
||||
} while (0)
|
||||
|
||||
static void dump_node(spl_ast_t *ast, spl_ast_node_ref_t node_ref, dump_stack_t *stack, bool last) {
|
||||
spl_ast_node_t *node = &ast->node_buckets.data[node_ref];
|
||||
|
||||
for (usize i = 0; i < vec_size(*stack); i++)
|
||||
printf("%s", vec_at(*stack, i) ? " " : "| ");
|
||||
printf("%s%s #%zu", last ? "`-" : "|-", spl_ast_kind_name(node->kind), node_ref);
|
||||
if (node->dbg.fname)
|
||||
printf(" [%d:%d]", node->dbg.line, node->dbg.col);
|
||||
printf("\n");
|
||||
|
||||
vec_push(*stack, last);
|
||||
|
||||
switch (node->kind) {
|
||||
case SPL_AST_ROOT:
|
||||
if (node->root.members)
|
||||
dump_node(ast, node->root.members, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_CONTAINER_MEMBERS:
|
||||
dump_vec(ast, node->container_members.external_tags, stack);
|
||||
dump_vec(ast, node->container_members.declarations, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_TAG_ITEM:
|
||||
case SPL_AST_EXTERNAL_TAG_ITEM:
|
||||
dump_vec(ast, node->tag_item.args, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_FN_DECL:
|
||||
dump_vec(ast, node->fn_decl.tag_list, stack);
|
||||
dump_vec(ast, node->fn_decl.params, stack);
|
||||
if (node->fn_decl.ret_type)
|
||||
dump_node(ast, node->fn_decl.ret_type, stack, false);
|
||||
if (node->fn_decl.body)
|
||||
dump_node(ast, node->fn_decl.body, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_VAR_DECL:
|
||||
case SPL_AST_CONST_DECL:
|
||||
dump_vec(ast, node->var_const_decl.attr_list, stack);
|
||||
if (node->var_const_decl.pattern)
|
||||
dump_node(ast, node->var_const_decl.pattern, stack, false);
|
||||
if (node->var_const_decl.type)
|
||||
dump_node(ast, node->var_const_decl.type, stack, false);
|
||||
if (node->var_const_decl.init_expr)
|
||||
dump_node(ast, node->var_const_decl.init_expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_DECL:
|
||||
dump_vec(ast, node->type_decl.attr_list, stack);
|
||||
if (node->type_decl.pattern)
|
||||
dump_node(ast, node->type_decl.pattern, stack, false);
|
||||
if (node->type_decl.type_expr)
|
||||
dump_node(ast, node->type_decl.type_expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_MEMBER_DECL:
|
||||
dump_vec(ast, node->member_decl.attr_list, stack);
|
||||
if (node->member_decl.type_expr)
|
||||
dump_node(ast, node->member_decl.type_expr, stack, false);
|
||||
if (node->member_decl.init_expr)
|
||||
dump_node(ast, node->member_decl.init_expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_PARAM_DECL:
|
||||
dump_vec(ast, node->param_decl.attr_list, stack);
|
||||
if (node->param_decl.type_expr)
|
||||
dump_node(ast, node->param_decl.type_expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_COMPTIME_DECL:
|
||||
if (node->comptime_decl.body)
|
||||
dump_node(ast, node->comptime_decl.body, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TOP_LEVEL_BLOCK:
|
||||
dump_vec(ast, node->top_level_block.tag_list, stack);
|
||||
if (node->top_level_block.block)
|
||||
dump_node(ast, node->top_level_block.block, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STATEMENT_RET:
|
||||
if (node->statement_ret.expr)
|
||||
dump_node(ast, node->statement_ret.expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STATEMENT_BREAK:
|
||||
if (node->statement_break.label)
|
||||
dump_node(ast, node->statement_break.label, stack, false);
|
||||
if (node->statement_break.expr)
|
||||
dump_node(ast, node->statement_break.expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STATEMENT_CONTINUE:
|
||||
if (node->statement_continue.label)
|
||||
dump_node(ast, node->statement_continue.label, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STATEMENT_DEFER:
|
||||
case SPL_AST_STATEMENT_COMPTIME:
|
||||
if (node->statement_defer.body)
|
||||
dump_node(ast, node->statement_defer.body, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STATEMENT_DECL:
|
||||
if (node->statement_decl.decl)
|
||||
dump_node(ast, node->statement_decl.decl, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STATEMENT_EXPR:
|
||||
if (node->statement_expr.expr)
|
||||
dump_node(ast, node->statement_expr.expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_AGGREGATE_MEMBER:
|
||||
dump_vec(ast, node->aggregate_member.attr_list, stack);
|
||||
if (node->aggregate_member.type_expr)
|
||||
dump_node(ast, node->aggregate_member.type_expr, stack, false);
|
||||
if (node->aggregate_member.init_expr)
|
||||
dump_node(ast, node->aggregate_member.init_expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_STRUCT_INIT_ITEM:
|
||||
if (node->struct_init_item.expr)
|
||||
dump_node(ast, node->struct_init_item.expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_MATCH_ARM:
|
||||
if (node->match_arm.pattern)
|
||||
dump_node(ast, node->match_arm.pattern, stack, false);
|
||||
if (node->match_arm.body)
|
||||
dump_node(ast, node->match_arm.body, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_LABEL:
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_INT_LIT:
|
||||
case SPL_AST_EXPR_FLOAT_LIT:
|
||||
case SPL_AST_EXPR_CHAR_LIT:
|
||||
case SPL_AST_EXPR_STRING_LIT:
|
||||
case SPL_AST_EXPR_BOOL_LIT:
|
||||
case SPL_AST_EXPR_NULL_LIT:
|
||||
case SPL_AST_EXPR_UNDEFINED_LIT:
|
||||
case SPL_AST_EXPR_IDENT:
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_UNARY:
|
||||
if (node->unary.operand)
|
||||
dump_node(ast, node->unary.operand, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_BINARY:
|
||||
if (node->binary.left)
|
||||
dump_node(ast, node->binary.left, stack, false);
|
||||
if (node->binary.right)
|
||||
dump_node(ast, node->binary.right, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_CALL:
|
||||
if (node->call.callee)
|
||||
dump_node(ast, node->call.callee, stack, false);
|
||||
dump_vec(ast, node->call.args, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_FIELD:
|
||||
if (node->field.obj)
|
||||
dump_node(ast, node->field.obj, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_INDEX:
|
||||
if (node->index.obj)
|
||||
dump_node(ast, node->index.obj, stack, false);
|
||||
if (node->index.index)
|
||||
dump_node(ast, node->index.index, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_SLICE:
|
||||
if (node->slice.obj)
|
||||
dump_node(ast, node->slice.obj, stack, false);
|
||||
if (node->slice.begin)
|
||||
dump_node(ast, node->slice.begin, stack, false);
|
||||
if (node->slice.end)
|
||||
dump_node(ast, node->slice.end, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_CAST:
|
||||
if (node->cast.expr)
|
||||
dump_node(ast, node->cast.expr, stack, false);
|
||||
if (node->cast.type)
|
||||
dump_node(ast, node->cast.type, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_TUPLE:
|
||||
dump_vec(ast, node->tuple.elems, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_ARRAY_LIT:
|
||||
if (node->array_literal.elem_type)
|
||||
dump_node(ast, node->array_literal.elem_type, stack, false);
|
||||
dump_vec(ast, node->array_literal.elems, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_AGGREGATE_LIT:
|
||||
dump_vec(ast, node->aggregate_literal.fields, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_COMPTIME_CALL:
|
||||
dump_vec(ast, node->comptime_call.args, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_BLOCK:
|
||||
dump_vec(ast, node->block.stmts, stack);
|
||||
if (node->block.tail_expr)
|
||||
dump_node(ast, node->block.tail_expr, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_IF:
|
||||
if (node->if_expr.cond)
|
||||
dump_node(ast, node->if_expr.cond, stack, false);
|
||||
if (node->if_expr.then_block)
|
||||
dump_node(ast, node->if_expr.then_block, stack, false);
|
||||
if (node->if_expr.else_branch)
|
||||
dump_node(ast, node->if_expr.else_branch, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_IFVAR:
|
||||
if (node->ifvar_expr.var_decl)
|
||||
dump_node(ast, node->ifvar_expr.var_decl, stack, false);
|
||||
if (node->ifvar_expr.then_block)
|
||||
dump_node(ast, node->ifvar_expr.then_block, stack, false);
|
||||
if (node->ifvar_expr.else_branch)
|
||||
dump_node(ast, node->ifvar_expr.else_branch, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_WHILE:
|
||||
if (node->while_expr.label)
|
||||
dump_node(ast, node->while_expr.label, stack, false);
|
||||
if (node->while_expr.cond)
|
||||
dump_node(ast, node->while_expr.cond, stack, false);
|
||||
if (node->while_expr.body)
|
||||
dump_node(ast, node->while_expr.body, stack, false);
|
||||
if (node->while_expr.else_branch)
|
||||
dump_node(ast, node->while_expr.else_branch, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_LOOP:
|
||||
if (node->loop_expr.label)
|
||||
dump_node(ast, node->loop_expr.label, stack, false);
|
||||
if (node->loop_expr.body)
|
||||
dump_node(ast, node->loop_expr.body, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_FOR:
|
||||
if (node->for_expr.label)
|
||||
dump_node(ast, node->for_expr.label, stack, false);
|
||||
dump_vec(ast, node->for_expr.iter_exprs, stack);
|
||||
if (node->for_expr.pattern)
|
||||
dump_node(ast, node->for_expr.pattern, stack, false);
|
||||
if (node->for_expr.body)
|
||||
dump_node(ast, node->for_expr.body, stack, false);
|
||||
if (node->for_expr.else_branch)
|
||||
dump_node(ast, node->for_expr.else_branch, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_MATCH:
|
||||
if (node->match_expr.matched_expr)
|
||||
dump_node(ast, node->match_expr.matched_expr, stack, false);
|
||||
dump_vec(ast, node->match_expr.arms, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_EXPR_FN_TYPE:
|
||||
dump_vec(ast, node->fn_type.params, stack);
|
||||
if (node->fn_type.ret_type)
|
||||
dump_node(ast, node->fn_type.ret_type, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_PATH:
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_POINTER:
|
||||
if (node->type_pointer.pointee)
|
||||
dump_node(ast, node->type_pointer.pointee, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_ARRAY:
|
||||
if (node->type_array.elem_type)
|
||||
dump_node(ast, node->type_array.elem_type, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_SLICE:
|
||||
if (node->type_slice.elem_type)
|
||||
dump_node(ast, node->type_slice.elem_type, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_FN:
|
||||
dump_vec(ast, node->type_fn.params, stack);
|
||||
if (node->type_fn.ret_type)
|
||||
dump_node(ast, node->type_fn.ret_type, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_PAREN:
|
||||
dump_vec(ast, node->type_paren.types, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_AGGREGATE:
|
||||
dump_vec(ast, node->type_aggregate.members, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_TYPE_COMPTIME_CALL:
|
||||
dump_vec(ast, node->type_comptime_call.args, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_PATTERN_IDENT:
|
||||
case SPL_AST_PATTERN_UNDERSCORE:
|
||||
break;
|
||||
|
||||
case SPL_AST_PATTERN_DOT:
|
||||
if (node->pattern_dot.sub_pattern)
|
||||
dump_node(ast, node->pattern_dot.sub_pattern, stack, true);
|
||||
break;
|
||||
|
||||
case SPL_AST_PATTERN_TUPLE:
|
||||
dump_vec(ast, node->pattern_tuple.patterns, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_PATTERN_LIST:
|
||||
dump_vec(ast, node->pattern_list.patterns, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_PATTERN_AGGREGATE:
|
||||
dump_vec(ast, node->pattern_aggregate.fields, stack);
|
||||
break;
|
||||
|
||||
case SPL_AST_PATTERN_FIELD:
|
||||
if (node->pattern_field.pattern)
|
||||
dump_node(ast, node->pattern_field.pattern, stack, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
(void)vec_pop(*stack);
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
#ifndef __SPL_AST_H__
|
||||
#define __SPL_AST_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_attr.h"
|
||||
#include "spl_dbg.h"
|
||||
#include "spl_tok.h"
|
||||
|
||||
/* clang-format off */
|
||||
#define SPL_AST_KIND_TABLE \
|
||||
X(SPL_AST_NONE, V0, none) \
|
||||
X(SPL_AST_ROOT, V0, root) \
|
||||
X(SPL_AST_CONTAINER_MEMBERS, V0, container_members) \
|
||||
X(SPL_AST_EXTERNAL_TAG_ITEM, V0, external_tag_item) \
|
||||
X(SPL_AST_TAG_ITEM, V0, tag_item) \
|
||||
X(SPL_AST_COMPTIME_DECL, V0, comptime_decl) \
|
||||
X(SPL_AST_TOP_LEVEL_BLOCK, V0, top_level_block) \
|
||||
X(SPL_AST_FN_DECL, V0, fn_decl) \
|
||||
X(SPL_AST_VAR_DECL, V0, var_decl) \
|
||||
X(SPL_AST_CONST_DECL, V0, const_decl) \
|
||||
X(SPL_AST_TYPE_DECL, V0, type_decl) \
|
||||
X(SPL_AST_MEMBER_DECL, V0, member_decl) \
|
||||
X(SPL_AST_PARAM_DECL, V0, param_decl) \
|
||||
X(SPL_AST_STATEMENT_RET, V0, statement_ret) \
|
||||
X(SPL_AST_STATEMENT_BREAK, V0, statement_break) \
|
||||
X(SPL_AST_STATEMENT_CONTINUE, V0, statement_continue) \
|
||||
X(SPL_AST_STATEMENT_DEFER, V0, statement_defer) \
|
||||
X(SPL_AST_STATEMENT_COMPTIME, V0, statement_comptime) \
|
||||
X(SPL_AST_STATEMENT_DECL, V0, statement_decl) \
|
||||
X(SPL_AST_STATEMENT_EXPR, V0, statement_expr) \
|
||||
X(SPL_AST_AGGREGATE_MEMBER, V0, aggregate_member) \
|
||||
X(SPL_AST_STRUCT_INIT_ITEM, V0, struct_init_item) \
|
||||
X(SPL_AST_MATCH_ARM, V0, match_arm) \
|
||||
X(SPL_AST_LABEL, V0, label) \
|
||||
X(SPL_AST_EXPR_INT_LIT, V0, int_lit) \
|
||||
X(SPL_AST_EXPR_FLOAT_LIT, V0, float_lit) \
|
||||
X(SPL_AST_EXPR_CHAR_LIT, V0, char_lit) \
|
||||
X(SPL_AST_EXPR_STRING_LIT, V0, string_lit) \
|
||||
X(SPL_AST_EXPR_BOOL_LIT, V0, bool_lit) \
|
||||
X(SPL_AST_EXPR_NULL_LIT, V0, null_lit) \
|
||||
X(SPL_AST_EXPR_UNDEFINED_LIT, V0, undefined_lit) \
|
||||
X(SPL_AST_EXPR_IDENT, V0, ident) \
|
||||
X(SPL_AST_EXPR_UNARY, V0, unary) \
|
||||
X(SPL_AST_EXPR_BINARY, V0, binary) \
|
||||
X(SPL_AST_EXPR_CALL, V0, call) \
|
||||
X(SPL_AST_EXPR_FIELD, V0, field) \
|
||||
X(SPL_AST_EXPR_INDEX, V0, index) \
|
||||
X(SPL_AST_EXPR_SLICE, V0, slice) \
|
||||
X(SPL_AST_EXPR_CAST, V0, cast) \
|
||||
X(SPL_AST_EXPR_TUPLE, V0, tuple) \
|
||||
X(SPL_AST_EXPR_ARRAY_LIT, V0, array_literal) \
|
||||
X(SPL_AST_EXPR_AGGREGATE_LIT, V0, aggregate_literal) \
|
||||
X(SPL_AST_EXPR_COMPTIME_CALL, V0, comptime_call) \
|
||||
X(SPL_AST_EXPR_BLOCK, V0, block) \
|
||||
X(SPL_AST_EXPR_IF, V0, if_expr) \
|
||||
X(SPL_AST_EXPR_IFVAR, V0, ifvar_expr) \
|
||||
X(SPL_AST_EXPR_WHILE, V0, while_expr) \
|
||||
X(SPL_AST_EXPR_LOOP, V0, loop_expr) \
|
||||
X(SPL_AST_EXPR_FOR, V0, for_expr) \
|
||||
X(SPL_AST_EXPR_MATCH, V0, match_expr) \
|
||||
X(SPL_AST_EXPR_FN_TYPE, V0, fn_type) \
|
||||
X(SPL_AST_TYPE_PATH, V0, type_path) \
|
||||
X(SPL_AST_TYPE_POINTER, V0, type_pointer) \
|
||||
X(SPL_AST_TYPE_ARRAY, V0, type_array) \
|
||||
X(SPL_AST_TYPE_SLICE, V0, type_slice) \
|
||||
X(SPL_AST_TYPE_FN, V0, type_fn) \
|
||||
X(SPL_AST_TYPE_PAREN, V0, type_paren) \
|
||||
X(SPL_AST_TYPE_AGGREGATE, V0, type_aggregate) \
|
||||
X(SPL_AST_TYPE_COMPTIME_CALL, V0, type_comptime_call) \
|
||||
X(SPL_AST_PATTERN_IDENT, V0, pattern_ident) \
|
||||
X(SPL_AST_PATTERN_UNDERSCORE, V0, pattern_underscore) \
|
||||
X(SPL_AST_PATTERN_DOT, V0, pattern_dot) \
|
||||
X(SPL_AST_PATTERN_TUPLE, V0, pattern_tuple) \
|
||||
X(SPL_AST_PATTERN_LIST, V0, pattern_list) \
|
||||
X(SPL_AST_PATTERN_AGGREGATE, V0, pattern_aggregate) \
|
||||
X(SPL_AST_PATTERN_FIELD, V0, pattern_field)
|
||||
/* clang-format on */
|
||||
|
||||
typedef enum {
|
||||
#ifdef X
|
||||
#undef X
|
||||
#endif
|
||||
#define X(name, ...) name,
|
||||
SPL_AST_KIND_TABLE
|
||||
#undef X
|
||||
SPL_AST_COUNT,
|
||||
} spl_ast_node_kind_t;
|
||||
|
||||
/* clang-format off */
|
||||
#define SPL_BINARY_OP_TABLE \
|
||||
X(SPL_OP_ASSIGN, 1, 'R', =) \
|
||||
X(SPL_OP_ASSIGN_ADD, 1, 'R', +=) \
|
||||
X(SPL_OP_ASSIGN_SUB, 1, 'R', -=) \
|
||||
X(SPL_OP_ASSIGN_MUL, 1, 'R', *=) \
|
||||
X(SPL_OP_ASSIGN_DIV, 1, 'R', /=) \
|
||||
X(SPL_OP_ASSIGN_MOD, 1, 'R', %=) \
|
||||
X(SPL_OP_ASSIGN_AND, 1, 'R', &=) \
|
||||
X(SPL_OP_ASSIGN_OR, 1, 'R', |=) \
|
||||
X(SPL_OP_ASSIGN_XOR, 1, 'R', ^=) \
|
||||
X(SPL_OP_ASSIGN_LSHIFT, 1, 'R', <<=) \
|
||||
X(SPL_OP_ASSIGN_RSHIFT, 1, 'R', >>=) \
|
||||
X(SPL_OP_BOOL_OR, 2, 'L', ||) \
|
||||
X(SPL_OP_BOOL_AND, 3, 'L', &&) \
|
||||
X(SPL_OP_BIT_OR, 4, 'L', |) \
|
||||
X(SPL_OP_BIT_XOR, 5, 'L', ^) \
|
||||
X(SPL_OP_BIT_AND, 6, 'L', &) \
|
||||
X(SPL_OP_CMP_EQ, 7, 'L', ==) \
|
||||
X(SPL_OP_CMP_NE, 7, 'L', !=) \
|
||||
X(SPL_OP_CMP_LE, 8, 'L', <=) \
|
||||
X(SPL_OP_CMP_GE, 8, 'L', >=) \
|
||||
X(SPL_OP_CMP_LT, 8, 'L', <) \
|
||||
X(SPL_OP_CMP_GT, 8, 'L', >) \
|
||||
X(SPL_OP_ARROW_LEFT, 9, 'L', <-) \
|
||||
X(SPL_OP_ARROW_RIGHT, 9, 'L', ->) \
|
||||
X(SPL_OP_ARROW_FAT_BOTH, 9, 'L', <=>) \
|
||||
X(SPL_OP_ARROW_THIN_BOTH, 9, 'L', <->) \
|
||||
X(SPL_OP_ARROW_FAT_RIGHT, 9, 'L', =>) \
|
||||
X(SPL_OP_RANGE, 10, 'L', ..) \
|
||||
X(SPL_OP_LSHIFT, 11, 'L', <<) \
|
||||
X(SPL_OP_RSHIFT, 11, 'L', >>) \
|
||||
X(SPL_OP_ADD, 12, 'L', +) \
|
||||
X(SPL_OP_SUB, 12, 'L', -) \
|
||||
X(SPL_OP_MUL, 13, 'L', *) \
|
||||
X(SPL_OP_DIV, 13, 'L', /) \
|
||||
X(SPL_OP_MOD, 13, 'L', %)
|
||||
/* clang-format on */
|
||||
|
||||
typedef enum {
|
||||
#ifdef X
|
||||
#undef X
|
||||
#endif
|
||||
#define X(name, ...) name,
|
||||
SPL_BINARY_OP_TABLE
|
||||
#undef X
|
||||
} spl_binary_op_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_UNARY_OP_MINUS,
|
||||
SPL_UNARY_OP_NOT,
|
||||
SPL_UNARY_OP_BIT_NOT,
|
||||
SPL_UNARY_OP_ADDRESS,
|
||||
SPL_UNARY_OP_DEREF,
|
||||
} spl_unary_op_t;
|
||||
|
||||
struct spl_ast_node;
|
||||
typedef struct spl_ast_node spl_ast_node_t;
|
||||
typedef usize spl_ast_node_ref_t;
|
||||
typedef VEC(spl_ast_node_ref_t) spl_ast_node_ref_vec_t;
|
||||
|
||||
struct spl_ast_node {
|
||||
spl_ast_node_kind_t kind;
|
||||
spl_dbg_node_t dbg;
|
||||
|
||||
usize resolved_def_id;
|
||||
usize resolved_lang_tid;
|
||||
usize expected_lang_tid;
|
||||
spl_attr_id_vec_t attrs;
|
||||
|
||||
union {
|
||||
struct {
|
||||
spl_ast_node_ref_t members;
|
||||
} root;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t external_tags;
|
||||
spl_ast_node_ref_vec_t declarations;
|
||||
} container_members;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t tag_list;
|
||||
bool is_comptime;
|
||||
const char *name;
|
||||
spl_ast_node_ref_vec_t params;
|
||||
spl_ast_node_ref_t ret_type;
|
||||
spl_ast_node_ref_t body;
|
||||
} fn_decl;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t attr_list;
|
||||
spl_ast_node_ref_t pattern;
|
||||
spl_ast_node_ref_t type;
|
||||
spl_ast_node_ref_t init_expr;
|
||||
} var_const_decl;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t attr_list;
|
||||
spl_ast_node_ref_t pattern;
|
||||
spl_ast_node_ref_t type_expr;
|
||||
} type_decl;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t attr_list;
|
||||
const char *name;
|
||||
spl_ast_node_ref_t type_expr;
|
||||
spl_ast_node_ref_t init_expr;
|
||||
} member_decl;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t attr_list;
|
||||
const char *name;
|
||||
spl_ast_node_ref_t type_expr;
|
||||
} param_decl;
|
||||
|
||||
struct {
|
||||
bool is_at;
|
||||
bool is_external;
|
||||
const char *name;
|
||||
spl_ast_node_ref_vec_t args;
|
||||
} tag_item;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t body;
|
||||
} comptime_decl;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t tag_list;
|
||||
spl_ast_node_ref_t block;
|
||||
} top_level_block;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t expr;
|
||||
} statement_ret;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t label;
|
||||
spl_ast_node_ref_t expr;
|
||||
} statement_break;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t label;
|
||||
} statement_continue;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t body;
|
||||
} statement_defer;
|
||||
struct {
|
||||
spl_ast_node_ref_t body;
|
||||
} statement_comptime;
|
||||
struct {
|
||||
spl_ast_node_ref_t decl;
|
||||
} statement_decl;
|
||||
struct {
|
||||
spl_ast_node_ref_t expr;
|
||||
} statement_expr;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t attr_list;
|
||||
const char *name;
|
||||
spl_ast_node_ref_t type_expr;
|
||||
spl_ast_node_ref_t init_expr;
|
||||
} aggregate_member;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_t expr;
|
||||
} struct_init_item;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t pattern;
|
||||
spl_ast_node_ref_t body;
|
||||
} match_arm;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
} label;
|
||||
|
||||
spl_mpa_int_t int_lit;
|
||||
spl_mpa_float_t float_lit;
|
||||
struct {
|
||||
char val;
|
||||
} char_lit;
|
||||
struct {
|
||||
const char *data;
|
||||
usize len;
|
||||
} string_lit;
|
||||
struct {
|
||||
bool val;
|
||||
} bool_lit;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
} ident;
|
||||
|
||||
struct {
|
||||
spl_unary_op_t op;
|
||||
spl_ast_node_ref_t operand;
|
||||
} unary;
|
||||
|
||||
struct {
|
||||
spl_binary_op_t op;
|
||||
spl_ast_node_ref_t left;
|
||||
spl_ast_node_ref_t right;
|
||||
} binary;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t callee;
|
||||
spl_ast_node_ref_vec_t args;
|
||||
} call;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t obj;
|
||||
const char *field;
|
||||
} field;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t obj;
|
||||
spl_ast_node_ref_t index;
|
||||
} index;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t obj;
|
||||
spl_ast_node_ref_t begin;
|
||||
spl_ast_node_ref_t end;
|
||||
} slice;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t expr;
|
||||
spl_ast_node_ref_t type;
|
||||
} cast;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t elems;
|
||||
} tuple;
|
||||
|
||||
struct {
|
||||
bool has_len;
|
||||
usize len;
|
||||
bool is_underscore_len;
|
||||
spl_ast_node_ref_t elem_type;
|
||||
spl_ast_node_ref_vec_t elems;
|
||||
} array_literal;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_vec_t fields;
|
||||
} aggregate_literal;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_vec_t args;
|
||||
} comptime_call;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t stmts;
|
||||
spl_ast_node_ref_t tail_expr;
|
||||
} block;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t cond;
|
||||
spl_ast_node_ref_t then_block;
|
||||
spl_ast_node_ref_t else_branch;
|
||||
} if_expr;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t var_decl;
|
||||
spl_ast_node_ref_t then_block;
|
||||
spl_ast_node_ref_t else_branch;
|
||||
} ifvar_expr;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t label;
|
||||
spl_ast_node_ref_t cond;
|
||||
spl_ast_node_ref_t body;
|
||||
spl_ast_node_ref_t else_branch;
|
||||
} while_expr;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t label;
|
||||
spl_ast_node_ref_t body;
|
||||
} loop_expr;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t label;
|
||||
spl_ast_node_ref_vec_t iter_exprs;
|
||||
spl_ast_node_ref_t pattern;
|
||||
spl_ast_node_ref_t body;
|
||||
spl_ast_node_ref_t else_branch;
|
||||
} for_expr;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t matched_expr;
|
||||
spl_ast_node_ref_vec_t arms;
|
||||
} match_expr;
|
||||
|
||||
struct {
|
||||
bool is_comptime;
|
||||
spl_ast_node_ref_vec_t params;
|
||||
spl_ast_node_ref_t ret_type;
|
||||
} fn_type;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_vec_t generic_args;
|
||||
} type_path;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t pointee;
|
||||
} type_pointer;
|
||||
|
||||
struct {
|
||||
bool has_len;
|
||||
isize len;
|
||||
bool is_underscore_len;
|
||||
spl_ast_node_ref_t elem_type;
|
||||
} type_array;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_t elem_type;
|
||||
} type_slice;
|
||||
|
||||
struct {
|
||||
bool is_comptime;
|
||||
spl_ast_node_ref_vec_t params;
|
||||
spl_ast_node_ref_t ret_type;
|
||||
} type_fn;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t types;
|
||||
} type_paren;
|
||||
|
||||
struct {
|
||||
const char *kind_name;
|
||||
spl_ast_node_ref_vec_t members;
|
||||
} type_aggregate;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_vec_t args;
|
||||
} type_comptime_call;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
} pattern_ident;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_t sub_pattern;
|
||||
} pattern_dot;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t patterns;
|
||||
} pattern_tuple;
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t patterns;
|
||||
} pattern_list;
|
||||
|
||||
struct {
|
||||
spl_ast_node_ref_vec_t fields;
|
||||
} pattern_aggregate;
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
spl_ast_node_ref_t pattern;
|
||||
} pattern_field;
|
||||
};
|
||||
};
|
||||
|
||||
typedef VEC(spl_ast_node_t) spl_ast_node_vec_t;
|
||||
typedef VEC(char *) spl_ast_cstr_ref_vec_t;
|
||||
typedef struct spl_ast {
|
||||
spl_tok_vec_t input;
|
||||
spl_ast_node_vec_t node_buckets;
|
||||
spl_ast_cstr_ref_vec_t str_buckets;
|
||||
spl_ast_node_ref_t root;
|
||||
} spl_ast_t;
|
||||
typedef VEC(spl_ast_t *) spl_ast_ptr_vec_t;
|
||||
|
||||
void spl_ast_init(spl_ast_t *ast, const spl_tok_vec_t *tok_vec /*move*/);
|
||||
void spl_ast_drop(spl_ast_t *ast);
|
||||
|
||||
spl_ast_node_t *spl_ast_node(spl_ast_t *ast, spl_ast_node_ref_t node);
|
||||
void spl_ast_dump(spl_ast_t *ast, spl_ast_node_ref_t node);
|
||||
|
||||
const char *spl_ast_kind_name(spl_ast_node_kind_t kind);
|
||||
const char *spl_binary_op_name(spl_binary_op_t op);
|
||||
const char *spl_unary_op_name(spl_unary_op_t op);
|
||||
|
||||
#endif /* __SPL_AST_H__ */
|
||||
+2206
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
#ifndef __SPL_AST2IR_H__
|
||||
#define __SPL_AST2IR_H__
|
||||
|
||||
#include "spl_diag.h"
|
||||
#include "spl_ir.h"
|
||||
#include "spl_sema.h"
|
||||
|
||||
typedef MAP(spl_def_id_t, spl_ir_node_ref_t) spl_def2ir_map_t;
|
||||
|
||||
typedef struct {
|
||||
usize cont_blk;
|
||||
usize defer_base;
|
||||
const char *label;
|
||||
spl_ir_node_ref_vec_t breaks;
|
||||
spl_ir_node_ref_t result_slot;
|
||||
spl_type_id_t result_tid;
|
||||
} spl_loop_ctx_t;
|
||||
typedef VEC(spl_loop_ctx_t) spl_loop_ctx_vec_t;
|
||||
|
||||
typedef VEC(spl_ast_node_ref_t) spl_defer_stack_t;
|
||||
|
||||
typedef struct {
|
||||
const spl_sema_t *sema;
|
||||
spl_ir_builder_t *ir;
|
||||
spl_ast_t *ast;
|
||||
spl_layout_t *layout; /* 借用 splc0 的 layout */
|
||||
spl_def2ir_map_t def2ir_map;
|
||||
spl_loop_ctx_vec_t loop_stack;
|
||||
spl_defer_stack_t defer_stack;
|
||||
spl_lang_type_id_t cur_ret_lang;
|
||||
usize cur_defer_base;
|
||||
|
||||
spl_diag_t *diag;
|
||||
} spl_ast2ir_t;
|
||||
|
||||
void spl_ast2ir_init(spl_ast2ir_t *ast2ir, spl_ir_builder_t *ir, spl_layout_t *layout,
|
||||
const spl_sema_t *sema);
|
||||
void spl_ast2ir_drop(spl_ast2ir_t *ast2ir);
|
||||
|
||||
void spl_ast2ir_run(spl_ast2ir_t *ast2ir, spl_ast_ptr_vec_t ast_vec);
|
||||
|
||||
#endif /* __SPL_AST2IR_H__ */
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "spl_attr.h"
|
||||
|
||||
static spl_attr_info_t spl_builtin_attrs[] = {
|
||||
{NULL, "extern", SPL_ATTR_STAGE_IR, SPL_ATTR_TARGET_DECL, SPL_ATTR_REQUIRED, 1, 1},
|
||||
{NULL, "inline", SPL_ATTR_STAGE_OPT, SPL_ATTR_TARGET_DECL, SPL_ATTR_OPTIONAL, 0, 0},
|
||||
{NULL, "noinline", SPL_ATTR_STAGE_OPT, SPL_ATTR_TARGET_DECL, SPL_ATTR_OPTIONAL, 0, 0},
|
||||
{NULL, "align", SPL_ATTR_STAGE_LAYOUT, SPL_ATTR_TARGET_TYPE, SPL_ATTR_REQUIRED, 1, 1},
|
||||
{NULL, "volatile", SPL_ATTR_STAGE_IR, SPL_ATTR_TARGET_EXPR, SPL_ATTR_REQUIRED, 0, 0},
|
||||
{NULL, "atomic", SPL_ATTR_STAGE_IR, SPL_ATTR_TARGET_EXPR, SPL_ATTR_REQUIRED, 0, 1},
|
||||
};
|
||||
|
||||
void spl_attr_init(spl_attr_t *ctx) {
|
||||
vec_init(ctx->table);
|
||||
vec_push(ctx->table, (spl_attr_node_t){0});
|
||||
vec_init(ctx->registry);
|
||||
spl_attr_register_builtins(ctx);
|
||||
}
|
||||
|
||||
void spl_attr_drop(spl_attr_t *ctx) {
|
||||
for (usize i = 1; i < vec_size(ctx->table); ++i)
|
||||
vec_free(vec_at(ctx->table, i).args);
|
||||
vec_free(ctx->table);
|
||||
vec_free(ctx->registry);
|
||||
}
|
||||
|
||||
spl_attr_id_t spl_attr_alloc(spl_attr_t *ctx, spl_attr_info_t *info, spl_attr_arg_vec_t args) {
|
||||
spl_attr_node_t a = {0};
|
||||
a.info = info;
|
||||
a.args = args;
|
||||
vec_push(ctx->table, a);
|
||||
return vec_size(ctx->table) - 1;
|
||||
}
|
||||
|
||||
spl_attr_node_t *spl_attr_node(const spl_attr_t *ctx, spl_attr_id_t id) {
|
||||
if (!id || id >= vec_size(ctx->table))
|
||||
return NULL;
|
||||
return &vec_at(ctx->table, id);
|
||||
}
|
||||
|
||||
void spl_attr_registry_add(spl_attr_t *ctx, spl_attr_info_t *info) {
|
||||
vec_push(ctx->registry, info);
|
||||
}
|
||||
|
||||
static int name_eq(const char *a, const char *b) {
|
||||
if (a == NULL && b == NULL)
|
||||
return 1;
|
||||
if (a == NULL || b == NULL)
|
||||
return 0;
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
spl_attr_info_t *spl_attr_lookup(spl_attr_t *ctx, const char *ns, const char *name) {
|
||||
for (usize i = 0; i < vec_size(ctx->registry); ++i) {
|
||||
spl_attr_info_t *info = vec_at(ctx->registry, i);
|
||||
if (name_eq(info->ns, ns) && strcmp(info->name, name) == 0)
|
||||
return info;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void spl_attr_register_builtins(spl_attr_t *ctx) {
|
||||
usize n = sizeof(spl_builtin_attrs) / sizeof(spl_builtin_attrs[0]);
|
||||
for (usize i = 0; i < n; ++i)
|
||||
spl_attr_registry_add(ctx, &spl_builtin_attrs[i]);
|
||||
}
|
||||
|
||||
static const char *attr_stage_name(spl_attr_stage_t s) {
|
||||
switch (s) {
|
||||
case SPL_ATTR_STAGE_SEMA:
|
||||
return "SEMA";
|
||||
case SPL_ATTR_STAGE_LAYOUT:
|
||||
return "LAYOUT";
|
||||
case SPL_ATTR_STAGE_IR:
|
||||
return "IR";
|
||||
case SPL_ATTR_STAGE_OPT:
|
||||
return "OPT";
|
||||
case SPL_ATTR_STAGE_BACKEND:
|
||||
return "BACKEND";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static const char *attr_target_name(spl_attr_target_t t) {
|
||||
switch (t) {
|
||||
case SPL_ATTR_TARGET_DECL:
|
||||
return "DECL";
|
||||
case SPL_ATTR_TARGET_TYPE:
|
||||
return "TYPE";
|
||||
case SPL_ATTR_TARGET_EXPR:
|
||||
return "EXPR";
|
||||
case SPL_ATTR_TARGET_STMT:
|
||||
return "STMT";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static const char *attr_demand_name(spl_attr_demand_t d) {
|
||||
return d == SPL_ATTR_REQUIRED ? "REQUIRED" : "OPTIONAL";
|
||||
}
|
||||
|
||||
void spl_attr_dump_id(const spl_attr_t *ctx, spl_attr_id_t id) {
|
||||
if (!ctx || !id) {
|
||||
printf("?");
|
||||
return;
|
||||
}
|
||||
spl_attr_node_t *a = spl_attr_node(ctx, id);
|
||||
if (!a || !a->info) {
|
||||
printf("?");
|
||||
return;
|
||||
}
|
||||
printf("%s", a->info->name);
|
||||
if (vec_size(a->args) > 0) {
|
||||
printf("(");
|
||||
for (usize i = 0; i < vec_size(a->args); ++i) {
|
||||
if (i > 0)
|
||||
printf(", ");
|
||||
spl_attr_arg_t *arg = &vec_at(a->args, i);
|
||||
switch (arg->kind) {
|
||||
case SPL_ATTR_ARG_INT:
|
||||
printf("%zd", arg->value.i);
|
||||
break;
|
||||
case SPL_ATTR_ARG_STR:
|
||||
printf("\"%s\"", arg->value.s ? arg->value.s : "");
|
||||
break;
|
||||
case SPL_ATTR_ARG_DEF:
|
||||
printf("def#%zu", arg->value.d);
|
||||
break;
|
||||
case SPL_ATTR_ARG_TYPE:
|
||||
printf("type#%zu", arg->value.t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf(")");
|
||||
}
|
||||
}
|
||||
|
||||
void spl_attr_dump(const spl_attr_t *ctx) {
|
||||
if (!ctx)
|
||||
return;
|
||||
printf("AttrTable:\n");
|
||||
for (usize i = 1; i < vec_size(ctx->table); ++i) {
|
||||
spl_attr_node_t *a = &vec_at(ctx->table, i);
|
||||
printf(" #%zu ", i);
|
||||
spl_attr_dump_id(ctx, i);
|
||||
if (a->info) {
|
||||
printf(" [stage=%s target=%s demand=%s consumed=0x%x]", attr_stage_name(a->info->stage),
|
||||
attr_target_name(a->info->target), attr_demand_name(a->info->demand),
|
||||
a->consumed_mask);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef __SPL_ATTR_H__
|
||||
#define __SPL_ATTR_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_mpa.h"
|
||||
|
||||
typedef enum {
|
||||
SPL_ATTR_STAGE_SEMA,
|
||||
SPL_ATTR_STAGE_LAYOUT,
|
||||
SPL_ATTR_STAGE_IR,
|
||||
SPL_ATTR_STAGE_OPT,
|
||||
SPL_ATTR_STAGE_BACKEND,
|
||||
} spl_attr_stage_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_ATTR_TARGET_DECL,
|
||||
SPL_ATTR_TARGET_TYPE,
|
||||
SPL_ATTR_TARGET_EXPR,
|
||||
SPL_ATTR_TARGET_STMT,
|
||||
} spl_attr_target_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_ATTR_OPTIONAL,
|
||||
SPL_ATTR_REQUIRED,
|
||||
} spl_attr_demand_t;
|
||||
|
||||
typedef struct {
|
||||
const char *ns;
|
||||
const char *name;
|
||||
spl_attr_stage_t stage;
|
||||
spl_attr_target_t target;
|
||||
spl_attr_demand_t demand;
|
||||
int min_args;
|
||||
int max_args;
|
||||
} spl_attr_info_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_ATTR_ARG_INT,
|
||||
SPL_ATTR_ARG_STR,
|
||||
SPL_ATTR_ARG_DEF,
|
||||
SPL_ATTR_ARG_TYPE,
|
||||
} spl_attr_arg_kind_t;
|
||||
|
||||
typedef struct {
|
||||
spl_attr_arg_kind_t kind;
|
||||
union {
|
||||
isize i;
|
||||
const char *s;
|
||||
usize d;
|
||||
usize t;
|
||||
} value;
|
||||
} spl_attr_arg_t;
|
||||
typedef VEC(spl_attr_arg_t) spl_attr_arg_vec_t;
|
||||
|
||||
typedef usize spl_attr_id_t;
|
||||
|
||||
typedef struct {
|
||||
spl_attr_info_t *info;
|
||||
spl_attr_arg_vec_t args;
|
||||
uint32_t consumed_mask;
|
||||
} spl_attr_node_t;
|
||||
typedef VEC(spl_attr_node_t) spl_attr_node_vec_t;
|
||||
typedef VEC(spl_attr_id_t) spl_attr_id_vec_t;
|
||||
|
||||
typedef VEC(spl_attr_info_t *) spl_attr_registry_t;
|
||||
|
||||
typedef struct {
|
||||
spl_attr_node_vec_t table;
|
||||
spl_attr_registry_t registry;
|
||||
} spl_attr_t;
|
||||
|
||||
void spl_attr_init(spl_attr_t *ctx);
|
||||
void spl_attr_drop(spl_attr_t *ctx);
|
||||
|
||||
spl_attr_id_t spl_attr_alloc(spl_attr_t *ctx, spl_attr_info_t *info, spl_attr_arg_vec_t args);
|
||||
spl_attr_node_t *spl_attr_node(const spl_attr_t *ctx, spl_attr_id_t id);
|
||||
|
||||
void spl_attr_registry_add(spl_attr_t *ctx, spl_attr_info_t *info);
|
||||
spl_attr_info_t *spl_attr_lookup(spl_attr_t *ctx, const char *ns, const char *name);
|
||||
void spl_attr_register_builtins(spl_attr_t *ctx);
|
||||
|
||||
void spl_attr_dump(const spl_attr_t *ctx);
|
||||
void spl_attr_dump_id(const spl_attr_t *ctx, spl_attr_id_t id);
|
||||
|
||||
#endif /* __SPL_ATTR_H__ */
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef __SPL_DBG_H__
|
||||
#define __SPL_DBG_H__
|
||||
|
||||
typedef struct {
|
||||
const char *fname;
|
||||
int line;
|
||||
int col;
|
||||
|
||||
const char *dbg_name;
|
||||
// TODO
|
||||
} spl_dbg_node_t;
|
||||
|
||||
#define SPL_FATAL(dbg, fmt, ...) \
|
||||
LOG_FATAL("fatal at %s:%d:%d " fmt, ((dbg) && (dbg)->fname ? (dbg)->fname : "<null>"), \
|
||||
((dbg) ? (dbg)->line : 0), ((dbg) ? (dbg)->col : 0), ##__VA_ARGS__)
|
||||
|
||||
#define SPL_ERROR(dbg, fmt, ...) \
|
||||
LOG_ERROR("error at %s:%d:%d " fmt, ((dbg) && (dbg)->fname ? (dbg)->fname : "<null>"), \
|
||||
((dbg) ? (dbg)->line : 0), ((dbg) ? (dbg)->col : 0), ##__VA_ARGS__)
|
||||
|
||||
#define SPL_WARN(dbg, fmt, ...) \
|
||||
LOG_ERROR("warn at %s:%d:%d " fmt, ((dbg) && (dbg)->fname ? (dbg)->fname : "<null>"), \
|
||||
((dbg) ? (dbg)->line : 0), ((dbg) ? (dbg)->col : 0), ##__VA_ARGS__)
|
||||
|
||||
#endif /* __SPL_DBG_H__ */
|
||||
@@ -0,0 +1,368 @@
|
||||
#include "spl_diag.h"
|
||||
#include "../stage0/include/color.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
static const char *const g_kind_names[] = {
|
||||
[SPL_DIAG_KIND_NONE] = "none",
|
||||
[SPL_DIAG_KIND_LEX_UNEXPECTED] = "lex_unexpected",
|
||||
[SPL_DIAG_KIND_PARSE_EXPECTED] = "parse_expected",
|
||||
[SPL_DIAG_KIND_PARSE_UNRECOGNIZED] = "parse_unrecognized",
|
||||
[SPL_DIAG_KIND_UNDEFINED_SYMBOL] = "undefined_symbol",
|
||||
[SPL_DIAG_KIND_DUPLICATE_SYMBOL] = "duplicate_symbol",
|
||||
[SPL_DIAG_KIND_TYPE_MISMATCH] = "type_mismatch",
|
||||
[SPL_DIAG_KIND_ARG_COUNT_MISMATCH] = "arg_count_mismatch",
|
||||
[SPL_DIAG_KIND_NOT_A_FUNCTION] = "not_a_function",
|
||||
[SPL_DIAG_KIND_NO_FIELD] = "no_field",
|
||||
[SPL_DIAG_KIND_NO_METHOD] = "no_method",
|
||||
[SPL_DIAG_KIND_INDEX_BASE_INVALID] = "index_base_invalid",
|
||||
[SPL_DIAG_KIND_TUPLE_INDEX_OOB] = "tuple_index_oob",
|
||||
[SPL_DIAG_KIND_FITS_OVERFLOW] = "fits_overflow",
|
||||
[SPL_DIAG_KIND_CIRCULAR_DEP] = "circular_dep",
|
||||
[SPL_DIAG_KIND_BREAK_OUTSIDE_LOOP] = "break_outside_loop",
|
||||
[SPL_DIAG_KIND_CONTINUE_OUTSIDE_LOOP] = "continue_outside_loop",
|
||||
[SPL_DIAG_KIND_UNDEFINED_LABEL] = "undefined_label",
|
||||
[SPL_DIAG_KIND_CAST_INVALID] = "cast_invalid",
|
||||
[SPL_DIAG_KIND_UNKNOWN_ATTR] = "unknown_attr",
|
||||
[SPL_DIAG_KIND_UNKNOWN_INTRIN] = "unknown_intrin",
|
||||
[SPL_DIAG_KIND_INTRIN_ARG_COUNT] = "intrin_arg_count",
|
||||
[SPL_DIAG_KIND_VAR_UNINITIALIZED] = "var_uninitialized",
|
||||
[SPL_DIAG_KIND_CANNOT_INFER] = "cannot_infer",
|
||||
[SPL_DIAG_KIND_MUST_RETURN_VALUE] = "must_return_value",
|
||||
[SPL_DIAG_KIND_UNHANDLED_NODE] = "unhandled_node",
|
||||
[SPL_DIAG_KIND_IMPLICIT_CAST_FAIL] = "implicit_cast_fail",
|
||||
[SPL_DIAG_KIND_NO_LVALUE] = "no_lvalue",
|
||||
[SPL_DIAG_KIND_BAD_FIELD_INDEX] = "bad_field_index",
|
||||
[SPL_DIAG_KIND_BAD_BLOCK] = "bad_block",
|
||||
[SPL_DIAG_KIND_IR_UNKNOWN_KIND] = "ir_unknown_kind",
|
||||
[SPL_DIAG_KIND_FRAME_OVERFLOW] = "frame_overflow",
|
||||
[SPL_DIAG_KIND_INTERNAL] = "internal",
|
||||
};
|
||||
|
||||
const char *spl_diag_kind_name(spl_diag_kind_t k) {
|
||||
if ((usize)k < sizeof(g_kind_names) / sizeof(g_kind_names[0]) && g_kind_names[k])
|
||||
return g_kind_names[k];
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static const char *const g_stage_names[] = {
|
||||
[SPL_STAGE_NONE] = "none", [SPL_STAGE_LEX] = "lex", [SPL_STAGE_PARSE] = "parse",
|
||||
[SPL_STAGE_SEMA] = "sema", [SPL_STAGE_AST2IR] = "ast2ir", [SPL_STAGE_IR2VM] = "ir2vm",
|
||||
[SPL_STAGE_DRIVER] = "driver",
|
||||
};
|
||||
|
||||
const char *spl_diag_stage_name(spl_diag_stage_t s) {
|
||||
if ((usize)s < sizeof(g_stage_names) / sizeof(g_stage_names[0]) && g_stage_names[s])
|
||||
return g_stage_names[s];
|
||||
return "?";
|
||||
}
|
||||
|
||||
const char *spl_diag_level_name(spl_diag_level_t l) {
|
||||
switch (l) {
|
||||
case SPL_DIAG_LEVEL_NOTE:
|
||||
return "note";
|
||||
case SPL_DIAG_LEVEL_WARN:
|
||||
return "warn";
|
||||
case SPL_DIAG_LEVEL_ERROR:
|
||||
return "error";
|
||||
case SPL_DIAG_LEVEL_FATAL:
|
||||
return "fatal";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static const char *level_color(spl_diag_level_t l) {
|
||||
switch (l) {
|
||||
case SPL_DIAG_LEVEL_NOTE:
|
||||
return ANSI_FG_CYAN;
|
||||
case SPL_DIAG_LEVEL_WARN:
|
||||
return ANSI_FG_YELLOW;
|
||||
case SPL_DIAG_LEVEL_ERROR:
|
||||
return ANSI_FG_RED;
|
||||
case SPL_DIAG_LEVEL_FATAL:
|
||||
return ANSI_FG_RED ANSI_UNDERLINED;
|
||||
}
|
||||
return ANSI_NONE;
|
||||
}
|
||||
|
||||
static char *diag_strdup(const char *s) {
|
||||
if (!s)
|
||||
return NULL;
|
||||
usize n = strlen(s);
|
||||
char *r = malloc(n + 1);
|
||||
if (!r)
|
||||
return NULL;
|
||||
memcpy(r, s, n);
|
||||
r[n] = '\0';
|
||||
return r;
|
||||
}
|
||||
|
||||
void spl_diag_init(spl_diag_t *d, void *fout) {
|
||||
memset(d, 0, sizeof *d);
|
||||
vec_init(d->ctx);
|
||||
vec_init(d->records);
|
||||
d->collect = true;
|
||||
d->fout = fout;
|
||||
d->color = true;
|
||||
d->stage = SPL_STAGE_NONE;
|
||||
}
|
||||
|
||||
static void frame_free(spl_diag_frame_t *f) {
|
||||
free(f->fn_name);
|
||||
free(f->label);
|
||||
f->fn_name = NULL;
|
||||
f->label = NULL;
|
||||
}
|
||||
|
||||
static void record_free(spl_diag_record_t *r) {
|
||||
free(r->message);
|
||||
r->message = NULL;
|
||||
for (usize i = 0; i < vec_size(r->ctx); ++i)
|
||||
frame_free(&vec_at(r->ctx, i));
|
||||
vec_free(r->ctx);
|
||||
for (usize i = 0; i < vec_size(r->notes); ++i)
|
||||
free(vec_at(r->notes, i).message);
|
||||
vec_free(r->notes);
|
||||
}
|
||||
|
||||
void spl_diag_reset(spl_diag_t *d) {
|
||||
for (usize i = 0; i < vec_size(d->ctx); ++i)
|
||||
frame_free(&vec_at(d->ctx, i));
|
||||
d->ctx.size = 0;
|
||||
|
||||
for (usize i = 0; i < vec_size(d->records); ++i)
|
||||
record_free(&vec_at(d->records, i));
|
||||
d->records.size = 0;
|
||||
|
||||
d->num_printed = 0;
|
||||
d->n_errors = 0;
|
||||
d->n_warns = 0;
|
||||
d->n_fatals = 0;
|
||||
}
|
||||
|
||||
void spl_diag_drop(spl_diag_t *d) {
|
||||
spl_diag_reset(d);
|
||||
vec_free(d->ctx);
|
||||
vec_free(d->records);
|
||||
}
|
||||
|
||||
void spl_diag_set_stage(spl_diag_t *d, spl_diag_stage_t stage) { d->stage = stage; }
|
||||
|
||||
void spl_diag_set_output(spl_diag_t *d, FILE *out) { d->fout = out; }
|
||||
|
||||
void spl_diag_set_color(spl_diag_t *d, bool enabled) { d->color = enabled; }
|
||||
|
||||
void spl_diag_set_collect(spl_diag_t *d, bool enabled) { d->collect = enabled; }
|
||||
|
||||
void spl_diag_push(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fn_name,
|
||||
const char *label) {
|
||||
spl_diag_frame_t f = {0};
|
||||
if (loc)
|
||||
f.loc = *loc;
|
||||
f.fn_name = fn_name ? diag_strdup(fn_name) : NULL;
|
||||
f.label = label ? diag_strdup(label) : NULL;
|
||||
vec_push(d->ctx, f);
|
||||
}
|
||||
|
||||
void spl_diag_pop(spl_diag_t *d) {
|
||||
if (vec_size(d->ctx) == 0)
|
||||
return;
|
||||
frame_free(&vec_at(d->ctx, vec_size(d->ctx) - 1));
|
||||
d->ctx.size--;
|
||||
}
|
||||
|
||||
usize spl_diag_depth(const spl_diag_t *d) { return vec_size(d->ctx); }
|
||||
|
||||
static spl_diag_frame_vec_t ctx_snapshot(const spl_diag_t *d) {
|
||||
spl_diag_frame_vec_t snap;
|
||||
vec_init(snap);
|
||||
for (usize i = 0; i < vec_size(d->ctx); ++i) {
|
||||
spl_diag_frame_t *src = &vec_at(d->ctx, i);
|
||||
spl_diag_frame_t f = {0};
|
||||
f.loc = src->loc;
|
||||
f.fn_name = src->fn_name ? diag_strdup(src->fn_name) : NULL;
|
||||
f.label = src->label ? diag_strdup(src->label) : NULL;
|
||||
vec_push(snap, f);
|
||||
}
|
||||
return snap;
|
||||
}
|
||||
|
||||
static void frame_desc(const spl_diag_frame_t *f, char *buf, usize cap) {
|
||||
if (f->fn_name && f->label)
|
||||
snprintf(buf, cap, "%s %s", f->label, f->fn_name);
|
||||
else if (f->fn_name)
|
||||
snprintf(buf, cap, "%s", f->fn_name);
|
||||
else if (f->label)
|
||||
snprintf(buf, cap, "%s", f->label);
|
||||
else
|
||||
snprintf(buf, cap, "?");
|
||||
}
|
||||
|
||||
static void print_loc(FILE *out, const spl_dbg_node_t *loc) {
|
||||
if (loc && loc->fname)
|
||||
fprintf(out, "%s:%d:%d", loc->fname, loc->line, loc->col);
|
||||
else
|
||||
fprintf(out, "<no-loc>");
|
||||
}
|
||||
|
||||
static void print_record(const spl_diag_t *d, const spl_diag_record_t *r) {
|
||||
FILE *out = (FILE *)d->fout;
|
||||
if (!out)
|
||||
return;
|
||||
const char *col = d->color ? level_color(r->level) : "";
|
||||
const char *rst = d->color ? ANSI_NONE : "";
|
||||
|
||||
fprintf(out, "%s%s[%s:%s]%s: ", col, spl_diag_level_name(r->level),
|
||||
spl_diag_stage_name(r->stage), spl_diag_kind_name(r->kind), rst);
|
||||
print_loc(out, &r->loc);
|
||||
fputc('\n', out);
|
||||
|
||||
fprintf(out, " %s\n", r->message ? r->message : "");
|
||||
|
||||
for (usize i = vec_size(r->ctx); i > 0; --i) {
|
||||
char buf[256];
|
||||
frame_desc(&vec_at(r->ctx, i - 1), buf, sizeof buf);
|
||||
fprintf(out, " in %s at ", buf);
|
||||
print_loc(out, &vec_at(r->ctx, i - 1).loc);
|
||||
fputc('\n', out);
|
||||
}
|
||||
|
||||
for (usize i = 0; i < vec_size(r->notes); ++i) {
|
||||
spl_diag_note_t *n = &vec_at(r->notes, i);
|
||||
const char *ncol = d->color ? ANSI_FG_CYAN : "";
|
||||
fprintf(out, "%snote%s: ", ncol, rst);
|
||||
print_loc(out, &n->loc);
|
||||
fputc('\n', out);
|
||||
fprintf(out, " %s\n", n->message ? n->message : "");
|
||||
}
|
||||
}
|
||||
|
||||
static void emit(spl_diag_t *d, spl_diag_level_t level, spl_diag_kind_t kind,
|
||||
const spl_dbg_node_t *loc, const char *fmt, va_list ap) {
|
||||
va_list ap2;
|
||||
va_copy(ap2, ap);
|
||||
int need = vsnprintf(NULL, 0, fmt, ap);
|
||||
if (need < 0)
|
||||
need = 0;
|
||||
|
||||
char *msg = malloc((usize)need + 1);
|
||||
if (msg)
|
||||
vsnprintf(msg, (usize)need + 1, fmt, ap2);
|
||||
va_end(ap2);
|
||||
|
||||
spl_diag_record_t rec = {0};
|
||||
rec.level = level;
|
||||
rec.stage = d->stage;
|
||||
rec.kind = kind;
|
||||
if (loc)
|
||||
rec.loc = *loc;
|
||||
rec.message = msg;
|
||||
rec.ctx = ctx_snapshot(d);
|
||||
vec_init(rec.notes);
|
||||
|
||||
if (level == SPL_DIAG_LEVEL_ERROR)
|
||||
d->n_errors++;
|
||||
else if (level == SPL_DIAG_LEVEL_FATAL) {
|
||||
d->n_errors++;
|
||||
d->n_fatals++;
|
||||
} else if (level == SPL_DIAG_LEVEL_WARN)
|
||||
d->n_warns++;
|
||||
|
||||
if (d->fout) {
|
||||
print_record(d, &rec);
|
||||
d->num_printed++;
|
||||
}
|
||||
|
||||
if (d->collect)
|
||||
vec_push(d->records, rec);
|
||||
else
|
||||
record_free(&rec);
|
||||
}
|
||||
|
||||
void spl_diag_note(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
emit(d, SPL_DIAG_LEVEL_NOTE, kind, loc, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void spl_diag_warn(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
emit(d, SPL_DIAG_LEVEL_WARN, kind, loc, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void spl_diag_error(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
emit(d, SPL_DIAG_LEVEL_ERROR, kind, loc, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void spl_diag_fatal(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
emit(d, SPL_DIAG_LEVEL_FATAL, kind, loc, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void spl_diag_attach_note(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fmt, ...) {
|
||||
if (vec_size(d->records) == 0)
|
||||
return;
|
||||
spl_diag_record_t *last = &vec_at(d->records, vec_size(d->records) - 1);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int need = vsnprintf(NULL, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
if (need < 0)
|
||||
need = 0;
|
||||
|
||||
char *msg = malloc((usize)need + 1);
|
||||
if (msg) {
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, (usize)need + 1, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
spl_diag_note_t n = {0};
|
||||
if (loc)
|
||||
n.loc = *loc;
|
||||
n.message = msg;
|
||||
vec_push(last->notes, n);
|
||||
}
|
||||
|
||||
bool spl_diag_has_error(const spl_diag_t *d) { return d->n_errors > 0; }
|
||||
|
||||
bool spl_diag_has_fatal(const spl_diag_t *d) { return d->n_fatals > 0; }
|
||||
|
||||
usize spl_diag_error_count(const spl_diag_t *d) { return d->n_errors; }
|
||||
|
||||
usize spl_diag_warn_count(const spl_diag_t *d) { return d->n_warns; }
|
||||
|
||||
void spl_diag_summary(const spl_diag_t *d) {
|
||||
FILE *out = (FILE *)d->fout;
|
||||
if (!out)
|
||||
return;
|
||||
|
||||
/* 把还没实时打印过的记录补打一遍。
|
||||
collect == false 时 records 是空的,num_printed 可能非零,
|
||||
用 records.size 兜底避免越界。 */
|
||||
usize printed_limit = d->num_printed;
|
||||
if (printed_limit > vec_size(d->records))
|
||||
printed_limit = vec_size(d->records);
|
||||
|
||||
for (usize i = printed_limit; i < vec_size(d->records); ++i)
|
||||
print_record(d, &vec_at(d->records, i));
|
||||
|
||||
/* 计数 */
|
||||
const char *ecol = d->color && d->n_errors > 0 ? ANSI_FG_RED : "";
|
||||
const char *rst = d->color ? ANSI_NONE : "";
|
||||
fprintf(out, "%s%zu error%s, %zu warning%s%s\n", ecol, d->n_errors, d->n_errors == 1 ? "" : "s",
|
||||
d->n_warns, d->n_warns == 1 ? "" : "s", rst);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef __SPL_DIAG_H__
|
||||
#define __SPL_DIAG_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_dbg.h"
|
||||
|
||||
typedef enum {
|
||||
SPL_DIAG_LEVEL_NOTE = 0,
|
||||
SPL_DIAG_LEVEL_WARN,
|
||||
SPL_DIAG_LEVEL_ERROR,
|
||||
SPL_DIAG_LEVEL_FATAL,
|
||||
} spl_diag_level_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_STAGE_NONE = 0,
|
||||
SPL_STAGE_LEX,
|
||||
SPL_STAGE_PARSE,
|
||||
SPL_STAGE_SEMA,
|
||||
SPL_STAGE_AST2IR,
|
||||
SPL_STAGE_IR2VM,
|
||||
SPL_STAGE_DRIVER,
|
||||
} spl_diag_stage_t;
|
||||
|
||||
/* kind 枚举:边写边补。新增时放对应分组末尾即可。 */
|
||||
typedef enum {
|
||||
SPL_DIAG_KIND_NONE = 0,
|
||||
|
||||
/* 词法 / 语法 */
|
||||
SPL_DIAG_KIND_LEX_UNEXPECTED,
|
||||
SPL_DIAG_KIND_PARSE_EXPECTED,
|
||||
SPL_DIAG_KIND_PARSE_UNRECOGNIZED,
|
||||
|
||||
/* sema */
|
||||
SPL_DIAG_KIND_UNDEFINED_SYMBOL,
|
||||
SPL_DIAG_KIND_DUPLICATE_SYMBOL,
|
||||
SPL_DIAG_KIND_TYPE_MISMATCH,
|
||||
SPL_DIAG_KIND_ARG_COUNT_MISMATCH,
|
||||
SPL_DIAG_KIND_NOT_A_FUNCTION,
|
||||
SPL_DIAG_KIND_NO_FIELD,
|
||||
SPL_DIAG_KIND_NO_METHOD,
|
||||
SPL_DIAG_KIND_INDEX_BASE_INVALID,
|
||||
SPL_DIAG_KIND_TUPLE_INDEX_OOB,
|
||||
SPL_DIAG_KIND_FITS_OVERFLOW,
|
||||
SPL_DIAG_KIND_CIRCULAR_DEP,
|
||||
SPL_DIAG_KIND_BREAK_OUTSIDE_LOOP,
|
||||
SPL_DIAG_KIND_CONTINUE_OUTSIDE_LOOP,
|
||||
SPL_DIAG_KIND_UNDEFINED_LABEL,
|
||||
SPL_DIAG_KIND_CAST_INVALID,
|
||||
SPL_DIAG_KIND_UNKNOWN_ATTR,
|
||||
SPL_DIAG_KIND_UNKNOWN_INTRIN,
|
||||
SPL_DIAG_KIND_INTRIN_ARG_COUNT,
|
||||
SPL_DIAG_KIND_VAR_UNINITIALIZED,
|
||||
SPL_DIAG_KIND_CANNOT_INFER,
|
||||
SPL_DIAG_KIND_MUST_RETURN_VALUE,
|
||||
|
||||
/* ast2ir */
|
||||
SPL_DIAG_KIND_UNHANDLED_NODE,
|
||||
SPL_DIAG_KIND_IMPLICIT_CAST_FAIL,
|
||||
SPL_DIAG_KIND_NO_LVALUE,
|
||||
SPL_DIAG_KIND_BAD_FIELD_INDEX,
|
||||
SPL_DIAG_KIND_BAD_BLOCK,
|
||||
|
||||
/* ir2vm */
|
||||
SPL_DIAG_KIND_IR_UNKNOWN_KIND,
|
||||
SPL_DIAG_KIND_FRAME_OVERFLOW,
|
||||
|
||||
/* 通用 */
|
||||
SPL_DIAG_KIND_INTERNAL,
|
||||
} spl_diag_kind_t;
|
||||
|
||||
typedef struct {
|
||||
spl_dbg_node_t loc;
|
||||
char *fn_name;
|
||||
char *label;
|
||||
} spl_diag_frame_t;
|
||||
typedef VEC(spl_diag_frame_t) spl_diag_frame_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_dbg_node_t loc;
|
||||
char *message;
|
||||
} spl_diag_note_t;
|
||||
typedef VEC(spl_diag_note_t) spl_diag_note_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_diag_level_t level;
|
||||
spl_diag_stage_t stage;
|
||||
spl_diag_kind_t kind;
|
||||
spl_dbg_node_t loc;
|
||||
char *message;
|
||||
spl_diag_frame_vec_t ctx;
|
||||
spl_diag_note_vec_t notes;
|
||||
} spl_diag_record_t;
|
||||
typedef VEC(spl_diag_record_t) spl_diag_record_vec_t;
|
||||
|
||||
typedef struct spl_diag {
|
||||
spl_diag_frame_vec_t ctx;
|
||||
spl_diag_record_vec_t records;
|
||||
usize num_printed;
|
||||
bool collect;
|
||||
usize n_errors;
|
||||
usize n_warns;
|
||||
usize n_fatals;
|
||||
spl_diag_stage_t stage;
|
||||
void *fout;
|
||||
bool color;
|
||||
} spl_diag_t;
|
||||
|
||||
void spl_diag_init(spl_diag_t *d, void *fout);
|
||||
void spl_diag_drop(spl_diag_t *d);
|
||||
void spl_diag_reset(spl_diag_t *d);
|
||||
void spl_diag_set_stage(spl_diag_t *d, spl_diag_stage_t stage);
|
||||
void spl_diag_set_output(spl_diag_t *d, FILE *out);
|
||||
void spl_diag_set_color(spl_diag_t *d, bool enabled);
|
||||
void spl_diag_set_collect(spl_diag_t *d, bool enabled);
|
||||
|
||||
void spl_diag_push(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fn_name,
|
||||
const char *label);
|
||||
void spl_diag_pop(spl_diag_t *d);
|
||||
usize spl_diag_depth(const spl_diag_t *d);
|
||||
|
||||
/* ============================================================
|
||||
* 发射
|
||||
*
|
||||
* 每个级别都生成独立的 spl_diag_record_t:
|
||||
* - 计入 records(collect=true 时)
|
||||
* - error/fatal 计入 n_errors,warn 计入 n_warns
|
||||
* - 立刻输出到 out(如果非 NULL)
|
||||
*
|
||||
* 四种级别的语义区别:
|
||||
* note —— 一般信息;不阻塞;用在前序诊断之外的独立说明
|
||||
* warn —— 警告;不阻塞;driver 可忽略
|
||||
* error —— 用户代码错误;调用者应 return 0
|
||||
* fatal —— 用户代码错误到数据结构已损坏;调用者必须 return 0
|
||||
* ============================================================ */
|
||||
void spl_diag_note(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...);
|
||||
void spl_diag_warn(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...);
|
||||
void spl_diag_error(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...);
|
||||
void spl_diag_fatal(spl_diag_t *d, spl_diag_kind_t kind, const spl_dbg_node_t *loc, const char *fmt,
|
||||
...);
|
||||
|
||||
/* 挂到最近一条记录(records 末尾)的 notes 列表下。
|
||||
* 不生成独立记录、不计入计数,输出时显示为:
|
||||
* note: <loc>
|
||||
* <msg>
|
||||
* 使用场景:辅助说明前一条诊断,例如
|
||||
* spl_diag_error(d, DUPLICATE_SYMBOL, &n->dbg, "duplicate symbol `foo`");
|
||||
* spl_diag_attach_note(d, &prev->dbg, "previously defined here");
|
||||
* 约束:只在 records 非空时生效;否则静默丢弃,不报错。 */
|
||||
void spl_diag_attach_note(spl_diag_t *d, const spl_dbg_node_t *loc, const char *fmt, ...);
|
||||
|
||||
bool spl_diag_has_error(const spl_diag_t *d);
|
||||
bool spl_diag_has_fatal(const spl_diag_t *d);
|
||||
usize spl_diag_error_count(const spl_diag_t *d);
|
||||
usize spl_diag_warn_count(const spl_diag_t *d);
|
||||
|
||||
void spl_diag_summary(const spl_diag_t *d);
|
||||
|
||||
const char *spl_diag_kind_name(spl_diag_kind_t k);
|
||||
const char *spl_diag_stage_name(spl_diag_stage_t s);
|
||||
const char *spl_diag_level_name(spl_diag_level_t l);
|
||||
|
||||
#endif /* __SPL_DIAG_H__ */
|
||||
@@ -0,0 +1,26 @@
|
||||
/* spl_dumptree.c 可配置的树形打印模块(只用基本 ASCII)*/
|
||||
|
||||
#include "spl_dumptree.h"
|
||||
#include <stdarg.h>
|
||||
|
||||
const spl_dumptree_style_t spl_dumptree_ascii_style = {
|
||||
"| ",
|
||||
"|-",
|
||||
"`-",
|
||||
" ",
|
||||
};
|
||||
|
||||
void spl_dumptree_print(const spl_dumptree_style_t *st, const char *prefix, int is_last,
|
||||
const char *fmt, ...) {
|
||||
va_list ap;
|
||||
printf("%s%s ", prefix, is_last ? st->last_branch : st->branch);
|
||||
va_start(ap, fmt);
|
||||
vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void spl_dumptree_child_prefix(const spl_dumptree_style_t *st, const char *prefix, int is_last,
|
||||
char *out, size_t cap) {
|
||||
snprintf(out, cap, "%s%s", prefix, is_last ? st->space : st->vertical);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* spl_dumptree.h 可配置的树形打印模块(只用基本 ASCII)*/
|
||||
|
||||
#ifndef __SPL_DUMPTREE_H__
|
||||
#define __SPL_DUMPTREE_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* 可配置的缩进字符串 */
|
||||
typedef struct {
|
||||
const char *vertical; /* "| " */
|
||||
const char *branch; /* "|-" */
|
||||
const char *last_branch; /* "`-" */
|
||||
const char *space; /* " " */
|
||||
} spl_dumptree_style_t;
|
||||
|
||||
/* 默认 ASCII 风格 */
|
||||
extern const spl_dumptree_style_t spl_dumptree_ascii_style;
|
||||
|
||||
/* 打印一行节点标签:prefix + 分支符 + label
|
||||
* prefix 已累积的缩进骨架(不含分支符)
|
||||
* is_last 本节点是否为同级最后一个子节点
|
||||
* fmt printf 风格 label */
|
||||
void spl_dumptree_print(const spl_dumptree_style_t *st, const char *prefix, int is_last,
|
||||
const char *fmt, ...);
|
||||
|
||||
/* 生成子节点的缩进骨架:parent_prefix + (parent_is_last ? space : vertical) */
|
||||
void spl_dumptree_child_prefix(const spl_dumptree_style_t *st, const char *prefix, int is_last,
|
||||
char *out, size_t cap);
|
||||
|
||||
#endif /* __SPL_DUMPTREE_H__ */
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef __SPL_EMIT_H__
|
||||
#define __SPL_EMIT_H__
|
||||
|
||||
#endif /* __SPL_EMIT_H__ */
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "spl_fold.h"
|
||||
|
||||
static usize spl_fold_bucket_hash(usize k) { return MAP_HASH_INT(k); }
|
||||
static int spl_fold_bucket_cmp(usize a, usize b) { return MAP_CMP_INT(a, b); }
|
||||
|
||||
void spl_fold_init(spl_fold_t *fold, void *ctx, spl_fold_eq_fn eq, spl_fold_next_fn next,
|
||||
spl_fold_set_next_fn set_next) {
|
||||
map_init(fold->buckets, spl_fold_bucket_hash, spl_fold_bucket_cmp);
|
||||
fold->ctx = ctx;
|
||||
fold->eq = eq;
|
||||
fold->next = next;
|
||||
fold->set_next = set_next;
|
||||
}
|
||||
|
||||
void spl_fold_drop(spl_fold_t *fold) { map_free(fold->buckets); }
|
||||
|
||||
spl_fold_id_t spl_fold_find(const spl_fold_t *fold, usize hash, const void *probe) {
|
||||
spl_fold_id_t cur = 0;
|
||||
if (!map_get(fold->buckets, hash, &cur))
|
||||
return 0;
|
||||
while (cur) {
|
||||
if (fold->eq(fold->ctx, cur, probe))
|
||||
return cur;
|
||||
cur = fold->next(fold->ctx, cur);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spl_fold_insert(spl_fold_t *fold, usize hash, spl_fold_id_t id) {
|
||||
spl_fold_id_t head = 0;
|
||||
map_get(fold->buckets, hash, &head);
|
||||
fold->set_next(fold->ctx, id, head);
|
||||
map_put(fold->buckets, hash, id);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef __SPL_FOLD_H__
|
||||
#define __SPL_FOLD_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
|
||||
/* 通用 intrusive 哈希集合,对应 LLVM FoldingSet 的桶链部分。
|
||||
- 节点表由调用方持有,节点自带 next 字段;
|
||||
- hash 只用于选择桶(本实现以完整 hash 值作桶 map 的 key),
|
||||
相等始终由 eq 回调做结构比较;
|
||||
- 只增不删,适合类型 intern。 */
|
||||
typedef usize spl_fold_id_t;
|
||||
|
||||
/* 相等返回非零;probe 指向调用方的临时(尚未入表)节点。 */
|
||||
typedef int (*spl_fold_eq_fn)(void *ctx, spl_fold_id_t node, const void *probe);
|
||||
typedef spl_fold_id_t (*spl_fold_next_fn)(void *ctx, spl_fold_id_t node);
|
||||
typedef void (*spl_fold_set_next_fn)(void *ctx, spl_fold_id_t node, spl_fold_id_t next);
|
||||
|
||||
typedef struct {
|
||||
MAP(usize, spl_fold_id_t) buckets;
|
||||
void *ctx;
|
||||
spl_fold_eq_fn eq;
|
||||
spl_fold_next_fn next;
|
||||
spl_fold_set_next_fn set_next;
|
||||
} spl_fold_t;
|
||||
|
||||
void spl_fold_init(spl_fold_t *fold, void *ctx, spl_fold_eq_fn eq, spl_fold_next_fn next,
|
||||
spl_fold_set_next_fn set_next);
|
||||
void spl_fold_drop(spl_fold_t *fold);
|
||||
|
||||
/* 命中返回已存在的 id,否则返回 0。 */
|
||||
spl_fold_id_t spl_fold_find(const spl_fold_t *fold, usize hash, const void *probe);
|
||||
|
||||
/* 把已分配 id 的节点挂到对应桶头。 */
|
||||
void spl_fold_insert(spl_fold_t *fold, usize hash, spl_fold_id_t id);
|
||||
|
||||
#endif /* __SPL_FOLD_H__ */
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "spl_intrin.h"
|
||||
|
||||
static const spl_intrin_info_t g_intrin_table[] = {
|
||||
[SPL_INTRIN_NONE] = {0},
|
||||
[SPL_INTRIN_BITSIZEOF] = {"@bitsizeof", SPL_INTRIN_BITSIZEOF, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_SIZEOF] = {"@sizeof", SPL_INTRIN_SIZEOF, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_ALIGNOF] = {"@alignof", SPL_INTRIN_ALIGNOF, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_ASSERT] = {"@assert", SPL_INTRIN_ASSERT, SPL_INTRIN_FORM_CALL, 1, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_BREAKPOINT] = {"@breakpoint", SPL_INTRIN_BREAKPOINT, SPL_INTRIN_FORM_CALL, 0, 0,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_LEN] = {"len", SPL_INTRIN_LEN, SPL_INTRIN_FORM_FIELD, 0, 0,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_INT_CAST] = {"@int_cast", SPL_INTRIN_INT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_TRUNCATE] = {"@truncate", SPL_INTRIN_TRUNCATE, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_BIT_CAST] = {"@bit_cast", SPL_INTRIN_BIT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_FLOAT_CAST] = {"@float_cast", SPL_INTRIN_FLOAT_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_FLOAT2INT] = {"@float2int", SPL_INTRIN_FLOAT2INT, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT2FLOAT] = {"@int2float", SPL_INTRIN_INT2FLOAT, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_BOOL2INT] = {"@bool2int", SPL_INTRIN_BOOL2INT, SPL_INTRIN_FORM_CALL, 1, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_PTR2INT] = {"@ptr2int", SPL_INTRIN_PTR2INT, SPL_INTRIN_FORM_CALL, 1, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_INT2PTR] = {"@int2ptr", SPL_INTRIN_INT2PTR, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_PTR_CAST] = {"@ptr_cast", SPL_INTRIN_PTR_CAST, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_UNIT},
|
||||
[SPL_INTRIN_ENUM2INT] = {"@enum2int", SPL_INTRIN_ENUM2INT, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_OFFSETOF] = {"@offsetof", SPL_INTRIN_OFFSETOF, SPL_INTRIN_FORM_CALL, 2, 2,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_FIELD_COUNT] = {"@field_count", SPL_INTRIN_FIELD_COUNT, SPL_INTRIN_FORM_CALL, 1, 1,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_FILE_NAME] = {"@file_name", SPL_INTRIN_FILE_NAME, SPL_INTRIN_FORM_CALL, 0, 0,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_LINE] = {"@line", SPL_INTRIN_LINE, SPL_INTRIN_FORM_CALL, 0, 0,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_COL] = {"@col", SPL_INTRIN_COL, SPL_INTRIN_FORM_CALL, 0, 0,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
[SPL_INTRIN_FN_NAME] = {"@fn_name", SPL_INTRIN_FN_NAME, SPL_INTRIN_FORM_CALL, 0, 0,
|
||||
SPL_INTRIN_RESULT_USIZE},
|
||||
};
|
||||
|
||||
static const usize g_intrin_count = sizeof(g_intrin_table) / sizeof(g_intrin_table[0]);
|
||||
|
||||
const spl_intrin_info_t *spl_intrin_info(spl_intrin_t kind) {
|
||||
if ((usize)kind >= g_intrin_count)
|
||||
return NULL;
|
||||
return &g_intrin_table[kind];
|
||||
}
|
||||
|
||||
spl_intrin_t spl_intrin_lookup_call(const char *name) {
|
||||
if (!name)
|
||||
return SPL_INTRIN_NONE;
|
||||
const char *n = name[0] == '@' ? name + 1 : name;
|
||||
for (usize i = 1; i < g_intrin_count; ++i) {
|
||||
const spl_intrin_info_t *d = &g_intrin_table[i];
|
||||
if (d->form != SPL_INTRIN_FORM_CALL)
|
||||
continue;
|
||||
const char *dn = d->name[0] == '@' ? d->name + 1 : d->name;
|
||||
if (strcmp(dn, n) == 0)
|
||||
return d->kind;
|
||||
}
|
||||
return SPL_INTRIN_NONE;
|
||||
}
|
||||
|
||||
spl_intrin_t spl_intrin_lookup_field(spl_lang_type_kind_t recv_kind, const char *field) {
|
||||
if (!field)
|
||||
return SPL_INTRIN_NONE;
|
||||
if (strcmp(field, "len") == 0) {
|
||||
if (recv_kind == SPL_LANG_TYPE_ARRAY || recv_kind == SPL_LANG_TYPE_SLICE)
|
||||
return SPL_INTRIN_LEN;
|
||||
}
|
||||
return SPL_INTRIN_NONE;
|
||||
}
|
||||
|
||||
spl_intrin_t spl_intrin_lookup_cast(spl_type_t *type, spl_type_id_t ft, spl_type_id_t tt) {
|
||||
/* 这是 `as` 的展开规则表。要调整 as 的语义,改这里。 */
|
||||
spl_type_node_t *f = spl_type_node(type, ft);
|
||||
spl_type_node_t *t = spl_type_node(type, tt);
|
||||
if (!f || !t)
|
||||
return SPL_INTRIN_NONE;
|
||||
|
||||
if (f->kind == SPL_TYPE_INT && t->kind == SPL_TYPE_INT) {
|
||||
usize fb = f->int_type.bits;
|
||||
usize tb = t->int_type.bits;
|
||||
if (fb == tb)
|
||||
return SPL_INTRIN_BIT_CAST;
|
||||
if (fb > tb)
|
||||
return SPL_INTRIN_TRUNCATE;
|
||||
return SPL_INTRIN_INT_CAST;
|
||||
}
|
||||
if (f->kind == SPL_TYPE_FLOAT && t->kind == SPL_TYPE_FLOAT)
|
||||
return SPL_INTRIN_FLOAT_CAST;
|
||||
if (f->kind == SPL_TYPE_BOOL && t->kind == SPL_TYPE_INT)
|
||||
return SPL_INTRIN_BOOL2INT;
|
||||
if (f->kind == SPL_TYPE_PTR && t->kind == SPL_TYPE_INT)
|
||||
return SPL_INTRIN_PTR2INT;
|
||||
if (f->kind == SPL_TYPE_INT && t->kind == SPL_TYPE_PTR)
|
||||
return SPL_INTRIN_INT2PTR;
|
||||
if (f->kind == SPL_TYPE_PTR && t->kind == SPL_TYPE_PTR)
|
||||
return SPL_INTRIN_PTR_CAST;
|
||||
|
||||
/* int float 刻意不在这里:只能通过显式 builtin 调用。
|
||||
enum<->int 也刻意禁止(用 @enum2int 取 tag)。 */
|
||||
return SPL_INTRIN_NONE;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef __SPL_INTRIN_H__
|
||||
#define __SPL_INTRIN_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_lang_type.h"
|
||||
|
||||
typedef enum {
|
||||
SPL_INTRIN_NONE = 0,
|
||||
SPL_INTRIN_BITSIZEOF,
|
||||
SPL_INTRIN_SIZEOF,
|
||||
SPL_INTRIN_ALIGNOF,
|
||||
SPL_INTRIN_ASSERT,
|
||||
SPL_INTRIN_BREAKPOINT,
|
||||
SPL_INTRIN_LEN,
|
||||
SPL_INTRIN_INT_CAST,
|
||||
SPL_INTRIN_TRUNCATE,
|
||||
SPL_INTRIN_BIT_CAST,
|
||||
SPL_INTRIN_FLOAT_CAST,
|
||||
SPL_INTRIN_FLOAT2INT,
|
||||
SPL_INTRIN_INT2FLOAT,
|
||||
SPL_INTRIN_BOOL2INT,
|
||||
SPL_INTRIN_PTR2INT,
|
||||
SPL_INTRIN_INT2PTR,
|
||||
SPL_INTRIN_PTR_CAST,
|
||||
SPL_INTRIN_ENUM2INT,
|
||||
SPL_INTRIN_OFFSETOF,
|
||||
SPL_INTRIN_FIELD_COUNT,
|
||||
/* 调用点位置信息(供日志/性能分析库使用) */
|
||||
SPL_INTRIN_FILE_NAME,
|
||||
SPL_INTRIN_LINE,
|
||||
SPL_INTRIN_COL,
|
||||
SPL_INTRIN_FN_NAME,
|
||||
} spl_intrin_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_INTRIN_FORM_CALL = 1, /* @sizeof(...) @assert(...) */
|
||||
SPL_INTRIN_FORM_FIELD = 2, /* recv.len */
|
||||
} spl_intrin_form_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_INTRIN_RESULT_UNIT,
|
||||
SPL_INTRIN_RESULT_BOOL,
|
||||
SPL_INTRIN_RESULT_USIZE,
|
||||
} spl_intrin_result_t;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
spl_intrin_t kind;
|
||||
spl_intrin_form_t form;
|
||||
int min_args; /* FIELD 形态忽略 */
|
||||
int max_args; /* -1 = variadic */
|
||||
spl_intrin_result_t result;
|
||||
} spl_intrin_info_t;
|
||||
|
||||
spl_intrin_t spl_intrin_lookup_call(const char *name);
|
||||
spl_intrin_t spl_intrin_lookup_field(spl_lang_type_kind_t recv_kind, const char *field);
|
||||
spl_intrin_t spl_intrin_lookup_cast(spl_type_t *type, spl_type_id_t ft, spl_type_id_t tt);
|
||||
|
||||
const spl_intrin_info_t *spl_intrin_info(spl_intrin_t kind);
|
||||
|
||||
/* ---- 与 IR 侧共享的类型布局约定 ---- */
|
||||
#define SPL_SLICE_PTR_FIELD_IDX 0
|
||||
#define SPL_SLICE_LEN_FIELD_IDX 1
|
||||
|
||||
#endif /* __SPL_INTRIN_H__ */
|
||||
+848
@@ -0,0 +1,848 @@
|
||||
#include "spl_ir.h"
|
||||
|
||||
const char *spl_ir_kind_name(spl_ir_kind_t kind) {
|
||||
static const char *const names[] = {
|
||||
#define X(a, b, c) #a,
|
||||
SPL_IR_FN_TABLE
|
||||
#undef X
|
||||
};
|
||||
if ((usize)kind < sizeof(names) / sizeof(names[0]))
|
||||
return names[kind];
|
||||
return "?";
|
||||
}
|
||||
|
||||
void spl_ir_init(spl_ir_t *ir) {
|
||||
vec_init(ir->funcs);
|
||||
vec_push(ir->funcs, (spl_ir_func_t){0});
|
||||
// vec_init(ir->gdata);
|
||||
// vec_push(ir->gdata, (spl_ir_node_t){0});
|
||||
}
|
||||
|
||||
static void node_drop_vecs(spl_ir_node_t *n) {
|
||||
if (!n)
|
||||
return;
|
||||
switch (n->kind) {
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
vec_free(n->agg_construct.fields);
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
vec_free(n->control_call.params);
|
||||
break;
|
||||
case SPL_IR_DBG_PANIC:
|
||||
free((char *)n->dbg_panic.msg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void spl_ir_drop(spl_ir_t *ir) {
|
||||
vec_for(ir->funcs, i) {
|
||||
spl_ir_func_t *f = &vec_at(ir->funcs, i);
|
||||
vec_for(f->nodes, j) {
|
||||
vec_free(vec_at(f->nodes, j).attrs);
|
||||
node_drop_vecs(&vec_at(f->nodes, j));
|
||||
}
|
||||
vec_free(f->attrs);
|
||||
vec_free(f->nodes);
|
||||
}
|
||||
vec_free(ir->funcs);
|
||||
// vec_for(ir->gdata, i) { node_drop_vecs(&vec_at(ir->gdata, i)); }
|
||||
// vec_free(ir->gdata);
|
||||
}
|
||||
|
||||
// 申请空的 ir_func 返回 id
|
||||
spl_ir_func_ref_t spl_ir_alloc_fn(spl_ir_t *ir) {
|
||||
if (ir == NULL) {
|
||||
return 0;
|
||||
}
|
||||
spl_ir_func_t f = {0};
|
||||
vec_init(f.nodes);
|
||||
vec_push(f.nodes, (spl_ir_node_t){0});
|
||||
vec_push(ir->funcs, f);
|
||||
return vec_size(ir->funcs) - 1;
|
||||
}
|
||||
|
||||
// 从 fn_id 申请空的 ir_node 返回 id
|
||||
spl_ir_node_ref_t spl_ir_alloc_node(spl_ir_t *ir, spl_ir_func_ref_t fn_id) {
|
||||
if (!fn_id || fn_id >= vec_size(ir->funcs)) {
|
||||
return 0;
|
||||
}
|
||||
spl_ir_func_t *f = &vec_at(ir->funcs, fn_id);
|
||||
spl_ir_node_t n = {0};
|
||||
vec_push(f->nodes, n);
|
||||
return vec_size(f->nodes) - 1;
|
||||
}
|
||||
|
||||
// 从 fn_id 里通过 node_id 获取 ir_node
|
||||
spl_ir_node_t *spl_ir_node(spl_ir_t *ir, spl_ir_func_ref_t fn_id, spl_ir_node_ref_t node_id) {
|
||||
if (!fn_id || fn_id >= vec_size(ir->funcs)) {
|
||||
return NULL;
|
||||
}
|
||||
spl_ir_func_t *f = &vec_at(ir->funcs, fn_id);
|
||||
if (!node_id || node_id >= vec_size(f->nodes)) {
|
||||
return NULL;
|
||||
}
|
||||
return &vec_at(f->nodes, node_id);
|
||||
}
|
||||
|
||||
// 从 fn_id 获取 ir_func
|
||||
spl_ir_func_t *spl_ir_func(spl_ir_t *ir, spl_ir_func_ref_t fn_id) {
|
||||
if (!fn_id || fn_id >= vec_size(ir->funcs)) {
|
||||
return NULL;
|
||||
}
|
||||
return &vec_at(ir->funcs, fn_id);
|
||||
}
|
||||
|
||||
static inline void ir_dump_type(const spl_type_t *ty, spl_type_id_t tid) {
|
||||
printf("(");
|
||||
spl_type_pure_dump((spl_type_t *)ty, tid);
|
||||
printf(")");
|
||||
}
|
||||
|
||||
static inline void ir_dump_ref(spl_ir_node_ref_t ref) {
|
||||
if (ref == 0) {
|
||||
printf(" _");
|
||||
} else {
|
||||
printf(" %%%zu", ref);
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_dump_node(const spl_type_t *ty, spl_ir_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_IR_ARITH_ADD:
|
||||
case SPL_IR_ARITH_SUB:
|
||||
case SPL_IR_ARITH_MUL:
|
||||
case SPL_IR_ARITH_DIV:
|
||||
case SPL_IR_ARITH_REM:
|
||||
case SPL_IR_ARITH_AND:
|
||||
case SPL_IR_ARITH_OR:
|
||||
case SPL_IR_ARITH_XOR:
|
||||
case SPL_IR_ARITH_SHL:
|
||||
case SPL_IR_ARITH_SHR:
|
||||
ir_dump_type(ty, n->arith.tid);
|
||||
ir_dump_ref(n->arith.left);
|
||||
ir_dump_ref(n->arith.right);
|
||||
break;
|
||||
case SPL_IR_ARITH_NEG:
|
||||
case SPL_IR_ARITH_ABS:
|
||||
case SPL_IR_ARITH_NOT:
|
||||
ir_dump_type(ty, n->arith.tid);
|
||||
ir_dump_ref(n->arith.left);
|
||||
break;
|
||||
case SPL_IR_CMP_EQ:
|
||||
case SPL_IR_CMP_NE:
|
||||
case SPL_IR_CMP_LT:
|
||||
case SPL_IR_CMP_LE:
|
||||
case SPL_IR_CMP_GT:
|
||||
case SPL_IR_CMP_GE:
|
||||
ir_dump_type(ty, n->cmp.tid);
|
||||
ir_dump_ref(n->cmp.a);
|
||||
ir_dump_ref(n->cmp.b);
|
||||
break;
|
||||
case SPL_IR_CAST_TRUNC:
|
||||
case SPL_IR_CAST_ZEXT:
|
||||
case SPL_IR_CAST_SEXT:
|
||||
case SPL_IR_CAST_FEXT:
|
||||
case SPL_IR_CAST_FTRUNC:
|
||||
case SPL_IR_CAST_BITCAST:
|
||||
case SPL_IR_CAST_PTR2INT:
|
||||
case SPL_IR_CAST_INT2PTR:
|
||||
case SPL_IR_CAST_BOOL2INT:
|
||||
case SPL_IR_CASE_INT2FLOAT:
|
||||
case SPL_IR_CASE_FLOAT2INT:
|
||||
printf(" ");
|
||||
spl_type_pure_dump((spl_type_t *)ty, n->cast.from_tid);
|
||||
printf(" -> ");
|
||||
spl_type_pure_dump((spl_type_t *)ty, n->cast.to_tid);
|
||||
ir_dump_ref(n->cast.val);
|
||||
break;
|
||||
case SPL_IR_MEM_ALLOCA:
|
||||
ir_dump_type(ty, n->mem_alloc.tid);
|
||||
ir_dump_ref(n->mem_alloc.count);
|
||||
break;
|
||||
case SPL_IR_MEM_GLOBAL_ALLOC:
|
||||
ir_dump_type(ty, n->mem_global_alloc.tid);
|
||||
printf(" g#%zu", n->mem_global_alloc.const_node);
|
||||
break;
|
||||
case SPL_IR_MEM_LOAD:
|
||||
ir_dump_type(ty, n->mem_load.tid);
|
||||
ir_dump_ref(n->mem_load.ptr);
|
||||
break;
|
||||
case SPL_IR_MEM_STORE:
|
||||
ir_dump_type(ty, n->mem_store.tid);
|
||||
ir_dump_ref(n->mem_store.ptr);
|
||||
ir_dump_ref(n->mem_store.val);
|
||||
break;
|
||||
case SPL_IR_MEM_OFFSET:
|
||||
ir_dump_type(ty, n->mem_offset.tid);
|
||||
ir_dump_ref(n->mem_offset.ptr);
|
||||
ir_dump_ref(n->mem_offset.offset);
|
||||
break;
|
||||
case SPL_IR_MEM_FIELD_PTR:
|
||||
ir_dump_type(ty, n->mem_field_ptr.tid);
|
||||
ir_dump_ref(n->mem_field_ptr.agg);
|
||||
printf(" #%zu", n->mem_field_ptr.field_idx);
|
||||
break;
|
||||
case SPL_IR_MEM_COPY:
|
||||
ir_dump_type(ty, n->mem_copy.tid);
|
||||
ir_dump_ref(n->mem_copy.dst);
|
||||
ir_dump_ref(n->mem_copy.src);
|
||||
ir_dump_ref(n->mem_copy.size);
|
||||
break;
|
||||
case SPL_IR_MEM_SET:
|
||||
ir_dump_type(ty, n->mem_set.tid);
|
||||
ir_dump_ref(n->mem_set.dst);
|
||||
ir_dump_ref(n->mem_set.val);
|
||||
ir_dump_ref(n->mem_set.size);
|
||||
break;
|
||||
case SPL_IR_TYPE_CONST: {
|
||||
ir_dump_type(ty, n->type_const.tid);
|
||||
spl_type_node_t *t = spl_type_node((spl_type_t *)ty, n->type_const.tid);
|
||||
if (t && t->kind == SPL_TYPE_FLOAT) {
|
||||
printf(" float=%f", n->type_const.float_lit);
|
||||
} else if (t && t->kind == SPL_TYPE_FN) {
|
||||
printf(" fn#%zu", n->type_const.fn);
|
||||
} else if (t && t->kind == SPL_TYPE_ARRAY) {
|
||||
/* 字节常量:不打印宿主指针(不确定),只标记 bytes。 */
|
||||
printf(" bytes");
|
||||
} else if (t && t->kind == SPL_TYPE_INT && t->int_type.bits == 8) {
|
||||
printf(" char='%c'", n->type_const.ch_lit);
|
||||
} else {
|
||||
printf(" int=%zu", n->type_const.int_lit);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SPL_IR_TYPE_BITSIZEOF:
|
||||
ir_dump_type(ty, n->bitsizeof.tid);
|
||||
break;
|
||||
case SPL_IR_TYPE_SIZEOF:
|
||||
ir_dump_type(ty, n->ir_sizeof.tid);
|
||||
break;
|
||||
case SPL_IR_TYPE_ALIGNOF:
|
||||
ir_dump_type(ty, n->ir_alignof.tid);
|
||||
break;
|
||||
case SPL_IR_TYPE_OFFSETOF:
|
||||
ir_dump_type(ty, n->ir_offsetof.tid);
|
||||
ir_dump_ref(n->ir_offsetof.field_idx);
|
||||
break;
|
||||
case SPL_IR_TYPE_FIELD_COUNT:
|
||||
ir_dump_type(ty, n->field_count.tid);
|
||||
break;
|
||||
case SPL_IR_AGG_CONSTRUCT:
|
||||
ir_dump_type(ty, n->agg_construct.tid);
|
||||
vec_for(n->agg_construct.fields, i) ir_dump_ref(vec_at(n->agg_construct.fields, i));
|
||||
break;
|
||||
case SPL_IR_AGG_EXTRACT:
|
||||
ir_dump_type(ty, n->agg_extract.tid);
|
||||
printf(" #%zu", n->agg_extract.field_idx);
|
||||
ir_dump_ref(n->agg_extract.val);
|
||||
break;
|
||||
case SPL_IR_AGG_INSERT:
|
||||
ir_dump_type(ty, n->agg_insert.tid);
|
||||
printf(" #%zu", n->agg_insert.field_idx);
|
||||
ir_dump_ref(n->agg_insert.agg);
|
||||
ir_dump_ref(n->agg_insert.field);
|
||||
break;
|
||||
case SPL_IR_CONTROL_SELECT:
|
||||
ir_dump_type(ty, n->control_select.tid);
|
||||
ir_dump_ref(n->control_select.cond);
|
||||
ir_dump_ref(n->control_select.true_val);
|
||||
ir_dump_ref(n->control_select.false_val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_BR:
|
||||
ir_dump_ref(n->control_br.cond);
|
||||
ir_dump_ref(n->control_br.true_label);
|
||||
ir_dump_ref(n->control_br.false_label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_JMP:
|
||||
ir_dump_ref(n->control_jmp.label);
|
||||
break;
|
||||
case SPL_IR_CONTROL_CALL:
|
||||
ir_dump_type(ty, n->control_call.tid);
|
||||
ir_dump_ref(n->control_call.func);
|
||||
vec_for(n->control_call.params, i) ir_dump_ref(vec_at(n->control_call.params, i));
|
||||
break;
|
||||
case SPL_IR_CONTROL_PARAM:
|
||||
ir_dump_type(ty, n->control_param.tid);
|
||||
ir_dump_ref(n->control_param.idx);
|
||||
break;
|
||||
case SPL_IR_CONTROL_RET:
|
||||
ir_dump_type(ty, n->control_ret.tid);
|
||||
ir_dump_ref(n->control_ret.val);
|
||||
break;
|
||||
case SPL_IR_CONTROL_UNREACHABLE:
|
||||
case SPL_IR_CONTROL_TRAP:
|
||||
case SPL_IR_DBG_BREAKPOINT:
|
||||
break;
|
||||
case SPL_IR_DBG_PANIC:
|
||||
printf(" \"%.*s\"", (int)n->dbg_panic.len, n->dbg_panic.msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_dump_attr_list(const spl_attr_t *attr, spl_attr_id_vec_t *ids) {
|
||||
if (!attr || !ids || vec_size(*ids) == 0)
|
||||
return;
|
||||
printf(" attrs=[");
|
||||
for (usize i = 0; i < vec_size(*ids); ++i) {
|
||||
if (i > 0)
|
||||
printf(", ");
|
||||
spl_attr_dump_id(attr, vec_at(*ids, i));
|
||||
}
|
||||
printf("]");
|
||||
}
|
||||
|
||||
void spl_ir_dump(spl_ir_t *ir, const spl_type_t *ty, const spl_attr_t *attr) {
|
||||
vec_for(ir->funcs, i) {
|
||||
if (i == 0)
|
||||
continue;
|
||||
spl_ir_func_t *func = &vec_at(ir->funcs, i);
|
||||
printf("func %s %%%zu", func->name ? func->name : "?", i);
|
||||
printf(" tid = ");
|
||||
spl_type_pure_dump((spl_type_t *)ty, func->fn_tid);
|
||||
ir_dump_attr_list(attr, &func->attrs);
|
||||
printf("\n");
|
||||
|
||||
vec_for(func->nodes, j) {
|
||||
if (j == 0)
|
||||
continue;
|
||||
spl_ir_node_t *node = &vec_at(func->nodes, j);
|
||||
printf(" #%zu ", j);
|
||||
printf("%s", spl_ir_kind_name(node->kind));
|
||||
ir_dump_node(ty, node);
|
||||
ir_dump_attr_list(attr, &node->attrs);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
// if (vec_size(ir->gdata) > 1) {
|
||||
// printf("gdata:\n");
|
||||
// vec_for(ir->gdata, i) {
|
||||
// if (i == 0)
|
||||
// continue;
|
||||
// spl_ir_node_t *node = &vec_at(ir->gdata, i);
|
||||
// printf(" #%zu ", i);
|
||||
// printf("%s", spl_ir_kind_name(node->kind));
|
||||
// ir_dump_node(ty, node);
|
||||
// printf("\n");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// IR Builder
|
||||
|
||||
// 从当前 fn 申请新的 node 并注入到当前基本块
|
||||
static spl_ir_node_ref_t builder_alloc_node(spl_ir_builder_t *b, spl_ir_kind_t kind) {
|
||||
Assert(!b->terminated && "emit into a terminated block");
|
||||
spl_ir_node_ref_t ref = spl_ir_alloc_node(&b->ir, b->current_fn);
|
||||
Assert(ref != 0);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->kind = kind;
|
||||
n->dbg = b->dbg;
|
||||
vec_init(n->attrs);
|
||||
|
||||
if (b->current_block >= vec_size(b->blocks)) {
|
||||
Panic("unknown bblock");
|
||||
return 0;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t *slot = &vec_at(b->blocks, b->current_block);
|
||||
if (*slot == 0)
|
||||
*slot = ref;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
void spl_ir_builder_init(spl_ir_builder_t *b) {
|
||||
spl_ir_init(&b->ir);
|
||||
b->current_fn = 0;
|
||||
b->dbg = (spl_dbg_node_t){0};
|
||||
vec_init(b->blocks);
|
||||
b->current_block = 0;
|
||||
vec_init(b->fixups);
|
||||
b->terminated = false;
|
||||
}
|
||||
|
||||
void spl_ir_builder_drop(spl_ir_builder_t *b) {
|
||||
spl_ir_drop(&b->ir);
|
||||
vec_free(b->blocks);
|
||||
vec_free(b->fixups);
|
||||
}
|
||||
|
||||
spl_ir_func_ref_t spl_ir_builder_fn_begin(spl_ir_builder_t *b, const char *name,
|
||||
spl_type_id_t fn_tid) {
|
||||
if (b->current_fn) {
|
||||
spl_ir_builder_fn_end(b);
|
||||
}
|
||||
spl_ir_func_ref_t fid = spl_ir_alloc_fn(&b->ir);
|
||||
spl_ir_func_t *f = spl_ir_func(&b->ir, fid);
|
||||
f->name = name;
|
||||
vec_init(f->attrs);
|
||||
f->fn_tid = fn_tid;
|
||||
|
||||
b->current_fn = fid;
|
||||
b->current_block = 0;
|
||||
b->blocks.size = 0;
|
||||
b->fixups.size = 0;
|
||||
b->terminated = false;
|
||||
return fid;
|
||||
}
|
||||
|
||||
spl_ir_func_ref_t spl_ir_builder_fn_reserve(spl_ir_builder_t *b, const char *name,
|
||||
spl_type_id_t fn_tid) {
|
||||
spl_ir_func_ref_t fid = spl_ir_alloc_fn(&b->ir);
|
||||
spl_ir_func_t *f = spl_ir_func(&b->ir, fid);
|
||||
f->name = name;
|
||||
vec_init(f->attrs);
|
||||
f->fn_tid = fn_tid;
|
||||
return fid;
|
||||
}
|
||||
|
||||
void spl_ir_builder_fn_begin_at(spl_ir_builder_t *b, spl_ir_func_ref_t fid) {
|
||||
if (b->current_fn && b->current_fn != fid) {
|
||||
spl_ir_builder_fn_end(b);
|
||||
}
|
||||
b->current_fn = fid;
|
||||
b->current_block = 0;
|
||||
b->blocks.size = 0;
|
||||
b->fixups.size = 0;
|
||||
b->terminated = false;
|
||||
}
|
||||
|
||||
void spl_ir_builder_fn_end(spl_ir_builder_t *b) {
|
||||
spl_ir_func_t *f = spl_ir_func(&b->ir, b->current_fn);
|
||||
if (!f) {
|
||||
Panic("ir func not found");
|
||||
return;
|
||||
}
|
||||
|
||||
vec_for(b->fixups, i) {
|
||||
spl_ir_fix_t *fx = &vec_at(b->fixups, i);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, fx->node);
|
||||
if (!n) {
|
||||
SPL_ERROR(&n->dbg, "unknown node in func");
|
||||
continue;
|
||||
}
|
||||
spl_ir_node_ref_t target = 0;
|
||||
if (fx->block < vec_size(b->blocks)) {
|
||||
target = vec_at(b->blocks, fx->block);
|
||||
}
|
||||
if (target == 0) {
|
||||
Panic("reloc to empty/unknown bblock #%zu", fx->block);
|
||||
}
|
||||
switch (fx->slot) {
|
||||
case SPL_IR_FIX_BR_TRUE:
|
||||
n->control_br.true_label = target;
|
||||
break;
|
||||
case SPL_IR_FIX_BR_FALSE:
|
||||
n->control_br.false_label = target;
|
||||
break;
|
||||
case SPL_IR_FIX_JMP:
|
||||
n->control_jmp.label = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
b->current_block = 0;
|
||||
b->blocks.size = 0;
|
||||
b->fixups.size = 0;
|
||||
b->terminated = false;
|
||||
}
|
||||
|
||||
usize spl_ir_builder_block_begin(spl_ir_builder_t *b) {
|
||||
vec_push(b->blocks, (spl_ir_block_t){0});
|
||||
usize block = vec_size(b->blocks) - 1;
|
||||
b->current_block = block;
|
||||
b->terminated = false;
|
||||
return block;
|
||||
}
|
||||
|
||||
void spl_ir_builder_block_end(spl_ir_builder_t *b) {
|
||||
if (!b->terminated) {
|
||||
Panic("bblock must be end of terminated");
|
||||
}
|
||||
if (b->current_block != 0 && b->current_block < vec_size(b->blocks) &&
|
||||
vec_at(b->blocks, b->current_block) == 0) {
|
||||
Panic("bblock #%zu is empty (no entry node emitted)", b->current_block);
|
||||
}
|
||||
}
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *builder, spl_type_id_t x_tid, \
|
||||
spl_ir_node_ref_t x_left, spl_ir_node_ref_t x_right) { \
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(builder, kind); \
|
||||
spl_ir_node_t *n = spl_ir_node(&builder->ir, builder->current_fn, ref); \
|
||||
n->arith.tid = x_tid; \
|
||||
n->arith.left = x_left; \
|
||||
n->arith.right = x_right; \
|
||||
return ref; \
|
||||
}
|
||||
SPL_IR_BIN_ARITH_TABLE
|
||||
#undef X
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *builder, spl_type_id_t x_tid, \
|
||||
spl_ir_node_ref_t x_val) { \
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(builder, kind); \
|
||||
spl_ir_node_t *n = spl_ir_node(&builder->ir, builder->current_fn, ref); \
|
||||
n->arith.tid = x_tid; \
|
||||
n->arith.left = x_val; \
|
||||
n->arith.right = 0; \
|
||||
return ref; \
|
||||
}
|
||||
SPL_IR_UN_ARITH_TABLE
|
||||
#undef X
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *builder, spl_type_id_t x_tid, \
|
||||
spl_ir_node_ref_t x_a, spl_ir_node_ref_t x_b) { \
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(builder, kind); \
|
||||
spl_ir_node_t *n = spl_ir_node(&builder->ir, builder->current_fn, ref); \
|
||||
n->cmp.tid = x_tid; \
|
||||
n->cmp.a = x_a; \
|
||||
n->cmp.b = x_b; \
|
||||
return ref; \
|
||||
}
|
||||
SPL_IR_CMP_TABLE
|
||||
#undef X
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *builder, spl_type_id_t x_from, \
|
||||
spl_type_id_t x_to, spl_ir_node_ref_t x_val) { \
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(builder, kind); \
|
||||
spl_ir_node_t *n = spl_ir_node(&builder->ir, builder->current_fn, ref); \
|
||||
n->cast.from_tid = x_from; \
|
||||
n->cast.to_tid = x_to; \
|
||||
n->cast.val = x_val; \
|
||||
return ref; \
|
||||
}
|
||||
SPL_IR_CAST_TABLE
|
||||
#undef X
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_alloca(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t count) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_ALLOCA);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_alloc.tid = tid;
|
||||
n->mem_alloc.count = count;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_global_alloc(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t const_node) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_GLOBAL_ALLOC);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_global_alloc.tid = tid;
|
||||
n->mem_global_alloc.const_node = const_node;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_load(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t ptr) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_LOAD);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_load.tid = tid;
|
||||
n->mem_load.ptr = ptr;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_store(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t ptr, spl_ir_node_ref_t val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_STORE);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_store.tid = tid;
|
||||
n->mem_store.ptr = ptr;
|
||||
n->mem_store.val = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_offset(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t ptr, spl_ir_node_ref_t offset) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_OFFSET);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_offset.tid = tid;
|
||||
n->mem_offset.ptr = ptr;
|
||||
n->mem_offset.offset = offset;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_field(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t agg, usize field_idx) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_FIELD_PTR);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_field_ptr.tid = tid;
|
||||
n->mem_field_ptr.agg = agg;
|
||||
n->mem_field_ptr.field_idx = field_idx;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_copy(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t dst, spl_ir_node_ref_t src,
|
||||
spl_ir_node_ref_t size) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_COPY);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_copy.tid = tid;
|
||||
n->mem_copy.dst = dst;
|
||||
n->mem_copy.src = src;
|
||||
n->mem_copy.size = size;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_set(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t dst, spl_ir_node_ref_t val,
|
||||
spl_ir_node_ref_t size) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_MEM_SET);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->mem_set.tid = tid;
|
||||
n->mem_set.dst = dst;
|
||||
n->mem_set.val = val;
|
||||
n->mem_set.size = size;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_bool(spl_ir_builder_t *b, spl_type_id_t tid, bool val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.bool_lit = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_ptr(spl_ir_builder_t *b, spl_type_id_t tid, void *val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.ptr_lit = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_int(spl_ir_builder_t *b, spl_type_id_t tid, usize val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.int_lit = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_float(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
double val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.float_lit = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_bytes(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
const void *data, usize len) {
|
||||
(void)len;
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.ptr_lit = (void *)data;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_char(spl_ir_builder_t *b, spl_type_id_t tid, char val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.ch_lit = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_fn(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_func_ref_t val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_CONST);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->type_const.tid = tid;
|
||||
n->type_const.fn = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_bitsizeof(spl_ir_builder_t *b, spl_type_id_t tid) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_BITSIZEOF);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->bitsizeof.tid = tid;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_sizeof(spl_ir_builder_t *b, spl_type_id_t tid) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_SIZEOF);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->ir_sizeof.tid = tid;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_alignof(spl_ir_builder_t *b, spl_type_id_t tid) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_ALIGNOF);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->ir_alignof.tid = tid;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_offsetof(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t field_idx) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_OFFSETOF);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->ir_offsetof.tid = tid;
|
||||
n->ir_offsetof.field_idx = field_idx;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_field_count(spl_ir_builder_t *b, spl_type_id_t tid) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_TYPE_FIELD_COUNT);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->field_count.tid = tid;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_agg_construct(spl_ir_builder_t *b, spl_type_id_t tid) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_AGG_CONSTRUCT);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->agg_construct.tid = tid;
|
||||
vec_init(n->agg_construct.fields);
|
||||
return ref;
|
||||
}
|
||||
|
||||
void spl_ir_builder_agg_construct_field(spl_ir_builder_t *b, spl_ir_node_ref_t node,
|
||||
spl_ir_node_ref_t field) {
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, node);
|
||||
Assert(n != NULL);
|
||||
vec_push(n->agg_construct.fields, field);
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_agg_extract(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_type_id_t field_tid, usize field_idx,
|
||||
spl_ir_node_ref_t val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_AGG_EXTRACT);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->agg_extract.tid = tid;
|
||||
n->agg_extract.field_tid = field_tid;
|
||||
n->agg_extract.field_idx = field_idx;
|
||||
n->agg_extract.val = val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_agg_insert(spl_ir_builder_t *b, spl_type_id_t tid, usize field_idx,
|
||||
spl_ir_node_ref_t agg, spl_ir_node_ref_t field) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_AGG_INSERT);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->agg_insert.tid = tid;
|
||||
n->agg_insert.field_idx = field_idx;
|
||||
n->agg_insert.agg = agg;
|
||||
n->agg_insert.field = field;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_select(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t cond, spl_ir_node_ref_t true_val,
|
||||
spl_ir_node_ref_t false_val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_SELECT);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->control_select.tid = tid;
|
||||
n->control_select.cond = cond;
|
||||
n->control_select.true_val = true_val;
|
||||
n->control_select.false_val = false_val;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_br(spl_ir_builder_t *b, spl_ir_node_ref_t cond) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_BR);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->control_br.cond = cond;
|
||||
n->control_br.true_label = 0;
|
||||
n->control_br.false_label = 0;
|
||||
b->terminated = true;
|
||||
return ref;
|
||||
}
|
||||
|
||||
void spl_ir_builder_control_br_fixup(spl_ir_builder_t *b, spl_ir_node_ref_t ref, usize true_block,
|
||||
usize false_block) {
|
||||
spl_ir_node_t *node = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
if (!(node && node->kind == SPL_IR_CONTROL_BR)) {
|
||||
Panic("fixup must be valid jmp ir node");
|
||||
}
|
||||
spl_ir_fix_t f1 = {.node = ref, .slot = SPL_IR_FIX_BR_TRUE, .block = true_block};
|
||||
vec_push(b->fixups, f1);
|
||||
spl_ir_fix_t f2 = {.node = ref, .slot = SPL_IR_FIX_BR_FALSE, .block = false_block};
|
||||
vec_push(b->fixups, f2);
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_jmp(spl_ir_builder_t *b) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_JMP);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->control_jmp.label = 0;
|
||||
b->terminated = true;
|
||||
return ref;
|
||||
}
|
||||
|
||||
void spl_ir_builder_control_jmp_fixup(spl_ir_builder_t *b, spl_ir_node_ref_t ref,
|
||||
usize target_block) {
|
||||
spl_ir_node_t *node = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
if (!(node && node->kind == SPL_IR_CONTROL_JMP)) {
|
||||
Panic("fixup must be valid jmp ir node");
|
||||
}
|
||||
spl_ir_fix_t fx = {.node = ref, .slot = SPL_IR_FIX_JMP, .block = target_block};
|
||||
vec_push(b->fixups, fx);
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_call(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t func) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_CALL);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->control_call.tid = tid;
|
||||
n->control_call.func = func;
|
||||
vec_init(n->control_call.params);
|
||||
return ref;
|
||||
}
|
||||
|
||||
void spl_ir_builder_control_call_param(spl_ir_builder_t *b, spl_ir_node_ref_t call_node,
|
||||
spl_ir_node_ref_t arg) {
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, call_node);
|
||||
Assert(n != NULL);
|
||||
vec_push(n->control_call.params, arg);
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_param(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t idx) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_PARAM);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->control_param.tid = tid;
|
||||
n->control_param.idx = idx;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_ret(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t val) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_RET);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->control_ret.tid = tid;
|
||||
n->control_ret.val = val;
|
||||
b->terminated = true;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_unreachable(spl_ir_builder_t *b) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_UNREACHABLE);
|
||||
b->terminated = true;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_trap(spl_ir_builder_t *b) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_CONTROL_TRAP);
|
||||
b->terminated = true;
|
||||
return ref;
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_dbg_breakpoint(spl_ir_builder_t *b) {
|
||||
return builder_alloc_node(b, SPL_IR_DBG_BREAKPOINT);
|
||||
}
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_dbg_panic(spl_ir_builder_t *b, const char *msg, usize len) {
|
||||
spl_ir_node_ref_t ref = builder_alloc_node(b, SPL_IR_DBG_PANIC);
|
||||
spl_ir_node_t *n = spl_ir_node(&b->ir, b->current_fn, ref);
|
||||
n->dbg_panic.msg = msg;
|
||||
n->dbg_panic.len = len;
|
||||
return ref;
|
||||
}
|
||||
+424
@@ -0,0 +1,424 @@
|
||||
#ifndef __SPL_IR_H__
|
||||
#define __SPL_IR_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_attr.h"
|
||||
#include "spl_dbg.h"
|
||||
#include "spl_type.h"
|
||||
|
||||
/* clang-format off */
|
||||
#define SPL_IR_FN_TABLE \
|
||||
X(arith.add, V0, SPL_IR_ARITH_ADD) \
|
||||
X(arith.sub, V0, SPL_IR_ARITH_SUB) \
|
||||
X(arith.mul, V0, SPL_IR_ARITH_MUL) \
|
||||
X(arith.div, V0, SPL_IR_ARITH_DIV) \
|
||||
X(arith.rem, V0, SPL_IR_ARITH_REM) \
|
||||
X(arith.neg, V0, SPL_IR_ARITH_NEG) \
|
||||
X(arith.abs, V0, SPL_IR_ARITH_ABS) \
|
||||
X(arith.and, V0, SPL_IR_ARITH_AND) \
|
||||
X(arith.or, V0, SPL_IR_ARITH_OR) \
|
||||
X(arith.xor, V0, SPL_IR_ARITH_XOR) \
|
||||
X(arith.shl, V0, SPL_IR_ARITH_SHL) \
|
||||
X(arith.shr, V0, SPL_IR_ARITH_SHR) \
|
||||
X(arith.not, V0, SPL_IR_ARITH_NOT) \
|
||||
X(cmp.eq, V0, SPL_IR_CMP_EQ) \
|
||||
X(cmp.ne, V0, SPL_IR_CMP_NE) \
|
||||
X(cmp.lt, V0, SPL_IR_CMP_LT) \
|
||||
X(cmp.le, V0, SPL_IR_CMP_LE) \
|
||||
X(cmp.gt, V0, SPL_IR_CMP_GT) \
|
||||
X(cmp.ge, V0, SPL_IR_CMP_GE) \
|
||||
X(cast.trunc, V0, SPL_IR_CAST_TRUNC) \
|
||||
X(cast.zext, V0, SPL_IR_CAST_ZEXT) \
|
||||
X(cast.sext, V0, SPL_IR_CAST_SEXT) \
|
||||
X(cast.fext, V0, SPL_IR_CAST_FEXT) \
|
||||
X(cast.ftrunc, V0, SPL_IR_CAST_FTRUNC) \
|
||||
X(cast.bitcast, V0, SPL_IR_CAST_BITCAST) \
|
||||
X(cast.ptr2int, V0, SPL_IR_CAST_PTR2INT) \
|
||||
X(cast.int2ptr, V0, SPL_IR_CAST_INT2PTR) \
|
||||
X(cast.bool2int, V0, SPL_IR_CAST_BOOL2INT) \
|
||||
X(case.int2float, V0, SPL_IR_CASE_INT2FLOAT) \
|
||||
X(case.float2int, V0, SPL_IR_CASE_FLOAT2INT) \
|
||||
X(mem.alloca, V0, SPL_IR_MEM_ALLOCA) \
|
||||
X(mem.global_alloc, V0, SPL_IR_MEM_GLOBAL_ALLOC) \
|
||||
X(mem.load, V0, SPL_IR_MEM_LOAD) \
|
||||
X(mem.store, V0, SPL_IR_MEM_STORE) \
|
||||
X(mem.offset, V0, SPL_IR_MEM_OFFSET) \
|
||||
X(mem.field, V0, SPL_IR_MEM_FIELD_PTR) \
|
||||
X(mem.copy, V0, SPL_IR_MEM_COPY) \
|
||||
X(mem.set, V0, SPL_IR_MEM_SET) \
|
||||
X(type.const, V0, SPL_IR_TYPE_CONST) \
|
||||
X(type.bitsizeof, V0, SPL_IR_TYPE_BITSIZEOF) \
|
||||
X(type.sizeof, V0, SPL_IR_TYPE_SIZEOF) \
|
||||
X(type.alignof, V0, SPL_IR_TYPE_ALIGNOF) \
|
||||
X(type.offsetof, V0, SPL_IR_TYPE_OFFSETOF) \
|
||||
X(type.field_count, V0, SPL_IR_TYPE_FIELD_COUNT) \
|
||||
X(agg.construct, V0, SPL_IR_AGG_CONSTRUCT) \
|
||||
X(agg.extract, V0, SPL_IR_AGG_EXTRACT) \
|
||||
X(agg.insert, V0, SPL_IR_AGG_INSERT) \
|
||||
X(control.select, V0, SPL_IR_CONTROL_SELECT) \
|
||||
X(control.br, V0, SPL_IR_CONTROL_BR) \
|
||||
X(control.jmp, V0, SPL_IR_CONTROL_JMP) \
|
||||
X(control.call, V0, SPL_IR_CONTROL_CALL) \
|
||||
X(control.param, V0, SPL_IR_CONTROL_PARAM) \
|
||||
X(control.ret, V0, SPL_IR_CONTROL_RET) \
|
||||
X(control.unreachable, V0, SPL_IR_CONTROL_UNREACHABLE) \
|
||||
X(control.trap, V0, SPL_IR_CONTROL_TRAP) \
|
||||
X(dbg.breakpoint, V0, SPL_IR_DBG_BREAKPOINT) \
|
||||
X(dbg.panic, V0, SPL_IR_DBG_PANIC) \
|
||||
|
||||
typedef enum {
|
||||
#ifdef X
|
||||
#undef X
|
||||
#endif
|
||||
#define X(a, b, c) c,
|
||||
SPL_IR_FN_TABLE
|
||||
#undef X
|
||||
} spl_ir_kind_t;
|
||||
/* clang-format on */
|
||||
|
||||
typedef usize spl_ir_node_ref_t; /* 0 is error */
|
||||
typedef VEC(spl_ir_node_ref_t) spl_ir_node_ref_vec_t;
|
||||
|
||||
typedef usize spl_ir_func_ref_t; /* 0 is error */
|
||||
|
||||
#define SPL_ENUM_TAG_FIELD_IDX 0
|
||||
|
||||
typedef struct {
|
||||
spl_ir_kind_t kind;
|
||||
spl_dbg_node_t dbg;
|
||||
spl_attr_id_vec_t attrs;
|
||||
union {
|
||||
spl_type_id_t tid;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t left;
|
||||
spl_ir_node_ref_t right;
|
||||
} arith;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t a;
|
||||
spl_ir_node_ref_t b;
|
||||
} cmp;
|
||||
struct {
|
||||
spl_type_id_t from_tid;
|
||||
spl_type_id_t to_tid;
|
||||
spl_ir_node_ref_t val;
|
||||
} cast;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t count;
|
||||
} mem_alloc;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t const_node;
|
||||
} mem_global_alloc;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t ptr;
|
||||
} mem_load;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t ptr;
|
||||
spl_ir_node_ref_t val;
|
||||
} mem_store;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t ptr;
|
||||
spl_ir_node_ref_t offset;
|
||||
} mem_offset;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t agg;
|
||||
usize field_idx;
|
||||
} mem_field_ptr;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t dst;
|
||||
spl_ir_node_ref_t src;
|
||||
spl_ir_node_ref_t size;
|
||||
} mem_copy;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t dst;
|
||||
spl_ir_node_ref_t val;
|
||||
spl_ir_node_ref_t size;
|
||||
} mem_set;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
union {
|
||||
bool bool_lit;
|
||||
void *ptr_lit;
|
||||
usize int_lit;
|
||||
double float_lit;
|
||||
// const char *cstr_lit;
|
||||
char ch_lit;
|
||||
spl_ir_func_ref_t fn;
|
||||
};
|
||||
} type_const;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
} bitsizeof;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
} ir_sizeof;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
} ir_alignof;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t field_idx;
|
||||
} ir_offsetof;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
} field_count;
|
||||
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_vec_t fields;
|
||||
} agg_construct;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_type_id_t field_tid;
|
||||
usize field_idx;
|
||||
spl_ir_node_ref_t val;
|
||||
} agg_extract;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
usize field_idx;
|
||||
spl_ir_node_ref_t agg;
|
||||
spl_ir_node_ref_t field;
|
||||
} agg_insert;
|
||||
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t cond;
|
||||
spl_ir_node_ref_t true_val;
|
||||
spl_ir_node_ref_t false_val;
|
||||
} control_select;
|
||||
struct {
|
||||
spl_ir_node_ref_t cond;
|
||||
spl_ir_node_ref_t true_label;
|
||||
spl_ir_node_ref_t false_label;
|
||||
} control_br;
|
||||
struct {
|
||||
spl_ir_node_ref_t label;
|
||||
} control_jmp;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t func;
|
||||
spl_ir_node_ref_vec_t params;
|
||||
} control_call;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t idx;
|
||||
} control_param;
|
||||
struct {
|
||||
spl_type_id_t tid;
|
||||
spl_ir_node_ref_t val;
|
||||
} control_ret;
|
||||
|
||||
struct {
|
||||
const char *msg;
|
||||
usize len;
|
||||
} dbg_panic;
|
||||
};
|
||||
} spl_ir_node_t;
|
||||
typedef VEC(spl_ir_node_t) spl_ir_node_vec_t;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
spl_attr_id_vec_t attrs;
|
||||
spl_type_id_t fn_tid;
|
||||
spl_ir_node_vec_t nodes;
|
||||
} spl_ir_func_t;
|
||||
typedef VEC(spl_ir_func_t) spl_ir_func_vec_t;
|
||||
|
||||
typedef spl_ir_node_ref_t spl_ir_block_t;
|
||||
typedef VEC(spl_ir_block_t) spl_ir_block_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_ir_node_ref_t node;
|
||||
enum { SPL_IR_FIX_BR_TRUE, SPL_IR_FIX_BR_FALSE, SPL_IR_FIX_JMP } slot;
|
||||
spl_ir_node_ref_t block;
|
||||
} spl_ir_fix_t;
|
||||
typedef VEC(spl_ir_fix_t) spl_ir_fix_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_ir_func_vec_t funcs;
|
||||
} spl_ir_t;
|
||||
|
||||
void spl_ir_init(spl_ir_t *ir);
|
||||
void spl_ir_drop(spl_ir_t *ir);
|
||||
|
||||
spl_ir_node_ref_t spl_ir_alloc_node(spl_ir_t *ir, spl_ir_func_ref_t fn_id);
|
||||
spl_ir_func_ref_t spl_ir_alloc_fn(spl_ir_t *ir);
|
||||
|
||||
spl_ir_node_t *spl_ir_node(spl_ir_t *ir, spl_ir_func_ref_t fn_id, spl_ir_node_ref_t node_id);
|
||||
spl_ir_func_t *spl_ir_func(spl_ir_t *ir, spl_ir_func_ref_t fn_id);
|
||||
|
||||
void spl_ir_dump(spl_ir_t *ir, const spl_type_t *ty, const spl_attr_t *attr);
|
||||
const char *spl_ir_kind_name(spl_ir_kind_t kind);
|
||||
|
||||
typedef struct {
|
||||
spl_ir_t ir;
|
||||
spl_dbg_node_t dbg;
|
||||
spl_ir_func_ref_t current_fn; /* 当前函数 */
|
||||
spl_ir_node_ref_t current_block; /* 当前发射块(0 = 未设) */
|
||||
spl_ir_block_vec_t blocks; /* 当前函数的基本块表 */
|
||||
spl_ir_fix_vec_t fixups; /* 跳转回填表 */
|
||||
bool terminated; /* 当前块已以 br/jmp/ret 终止 */
|
||||
} spl_ir_builder_t;
|
||||
|
||||
/* clang-format off */
|
||||
#define SPL_IR_BIN_ARITH_TABLE \
|
||||
X(arith_add, SPL_IR_ARITH_ADD) \
|
||||
X(arith_sub, SPL_IR_ARITH_SUB) \
|
||||
X(arith_mul, SPL_IR_ARITH_MUL) \
|
||||
X(arith_div, SPL_IR_ARITH_DIV) \
|
||||
X(arith_rem, SPL_IR_ARITH_REM) \
|
||||
X(arith_and, SPL_IR_ARITH_AND) \
|
||||
X(arith_or, SPL_IR_ARITH_OR) \
|
||||
X(arith_xor, SPL_IR_ARITH_XOR) \
|
||||
X(arith_shl, SPL_IR_ARITH_SHL) \
|
||||
X(arith_shr, SPL_IR_ARITH_SHR)
|
||||
#define SPL_IR_UN_ARITH_TABLE \
|
||||
X(arith_neg, SPL_IR_ARITH_NEG) \
|
||||
X(arith_abs, SPL_IR_ARITH_ABS) \
|
||||
X(arith_not, SPL_IR_ARITH_NOT)
|
||||
#define SPL_IR_CMP_TABLE \
|
||||
X(cmp_eq, SPL_IR_CMP_EQ) \
|
||||
X(cmp_ne, SPL_IR_CMP_NE) \
|
||||
X(cmp_lt, SPL_IR_CMP_LT) \
|
||||
X(cmp_le, SPL_IR_CMP_LE) \
|
||||
X(cmp_gt, SPL_IR_CMP_GT) \
|
||||
X(cmp_ge, SPL_IR_CMP_GE)
|
||||
#define SPL_IR_CAST_TABLE \
|
||||
X(cast_trunc, SPL_IR_CAST_TRUNC) \
|
||||
X(cast_zext, SPL_IR_CAST_ZEXT) \
|
||||
X(cast_sext, SPL_IR_CAST_SEXT) \
|
||||
X(cast_fext, SPL_IR_CAST_FEXT) \
|
||||
X(cast_ftrunc, SPL_IR_CAST_FTRUNC) \
|
||||
X(cast_bitcast, SPL_IR_CAST_BITCAST) \
|
||||
X(cast_ptr2int, SPL_IR_CAST_PTR2INT) \
|
||||
X(cast_int2ptr, SPL_IR_CAST_INT2PTR) \
|
||||
X(cast_bool2int, SPL_IR_CAST_BOOL2INT) \
|
||||
X(case_int2float, SPL_IR_CASE_INT2FLOAT) \
|
||||
X(case_float2int, SPL_IR_CASE_FLOAT2INT)
|
||||
/* clang-format on */
|
||||
void spl_ir_builder_init(spl_ir_builder_t *b);
|
||||
void spl_ir_builder_drop(spl_ir_builder_t *b);
|
||||
|
||||
spl_ir_func_ref_t spl_ir_builder_fn_begin(spl_ir_builder_t *b, const char *name,
|
||||
spl_type_id_t fn_tid);
|
||||
spl_ir_func_ref_t spl_ir_builder_fn_reserve(spl_ir_builder_t *b, const char *name,
|
||||
spl_type_id_t fn_tid);
|
||||
void spl_ir_builder_fn_begin_at(spl_ir_builder_t *b, spl_ir_func_ref_t fid);
|
||||
void spl_ir_builder_fn_end(spl_ir_builder_t *b);
|
||||
|
||||
usize spl_ir_builder_block_begin(spl_ir_builder_t *b);
|
||||
void spl_ir_builder_block_end(spl_ir_builder_t *b);
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *b, spl_type_id_t tid, \
|
||||
spl_ir_node_ref_t left, spl_ir_node_ref_t right);
|
||||
SPL_IR_BIN_ARITH_TABLE
|
||||
#undef X
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *b, spl_type_id_t tid, \
|
||||
spl_ir_node_ref_t val);
|
||||
SPL_IR_UN_ARITH_TABLE
|
||||
#undef X
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *builder, spl_type_id_t tid, \
|
||||
spl_ir_node_ref_t a, spl_ir_node_ref_t b);
|
||||
SPL_IR_CMP_TABLE
|
||||
#undef X
|
||||
|
||||
#define X(name, kind) \
|
||||
spl_ir_node_ref_t spl_ir_builder_##name(spl_ir_builder_t *b, spl_type_id_t from_tid, \
|
||||
spl_type_id_t to_tid, spl_ir_node_ref_t val);
|
||||
SPL_IR_CAST_TABLE
|
||||
#undef X
|
||||
|
||||
/* memory */
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_alloca(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t count);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_global_alloc(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t const_node);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_load(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t ptr);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_store(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t ptr, spl_ir_node_ref_t val);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_offset(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t ptr, spl_ir_node_ref_t offset);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_field(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t agg, usize field_idx);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_copy(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t dst, spl_ir_node_ref_t src,
|
||||
spl_ir_node_ref_t size);
|
||||
spl_ir_node_ref_t spl_ir_builder_mem_set(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t dst, spl_ir_node_ref_t val,
|
||||
spl_ir_node_ref_t size);
|
||||
|
||||
/* type const */
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_ptr(spl_ir_builder_t *b, spl_type_id_t tid, void *val);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_bool(spl_ir_builder_t *b, spl_type_id_t tid, bool val);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_int(spl_ir_builder_t *b, spl_type_id_t tid, usize val);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_float(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
double val);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_bytes(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
const void *data, usize len);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_char(spl_ir_builder_t *b, spl_type_id_t tid, char val);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_const_fn(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_func_ref_t val);
|
||||
|
||||
/* type */
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_type_bitsizeof(spl_ir_builder_t *b, spl_type_id_t tid);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_sizeof(spl_ir_builder_t *b, spl_type_id_t tid);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_alignof(spl_ir_builder_t *b, spl_type_id_t tid);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_offsetof(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t field_idx);
|
||||
spl_ir_node_ref_t spl_ir_builder_type_field_count(spl_ir_builder_t *b, spl_type_id_t tid);
|
||||
|
||||
/* agg */
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_agg_construct(spl_ir_builder_t *b, spl_type_id_t tid);
|
||||
void spl_ir_builder_agg_construct_field(spl_ir_builder_t *b, spl_ir_node_ref_t node,
|
||||
spl_ir_node_ref_t field);
|
||||
spl_ir_node_ref_t spl_ir_builder_agg_extract(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_type_id_t field_tid, usize field_idx,
|
||||
spl_ir_node_ref_t val);
|
||||
spl_ir_node_ref_t spl_ir_builder_agg_insert(spl_ir_builder_t *b, spl_type_id_t tid, usize field_idx,
|
||||
spl_ir_node_ref_t agg, spl_ir_node_ref_t field);
|
||||
|
||||
/* control */
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_control_select(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t cond, spl_ir_node_ref_t true_val,
|
||||
spl_ir_node_ref_t false_val);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_br(spl_ir_builder_t *b, spl_ir_node_ref_t cond);
|
||||
void spl_ir_builder_control_br_fixup(spl_ir_builder_t *b, spl_ir_node_ref_t ref, usize true_block,
|
||||
usize false_block);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_jmp(spl_ir_builder_t *b);
|
||||
void spl_ir_builder_control_jmp_fixup(spl_ir_builder_t *b, spl_ir_node_ref_t ref,
|
||||
usize target_block);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_call(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t func);
|
||||
void spl_ir_builder_control_call_param(spl_ir_builder_t *b, spl_ir_node_ref_t call_node,
|
||||
spl_ir_node_ref_t arg);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_param(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t idx);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_ret(spl_ir_builder_t *b, spl_type_id_t tid,
|
||||
spl_ir_node_ref_t val);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_unreachable(spl_ir_builder_t *b);
|
||||
spl_ir_node_ref_t spl_ir_builder_control_trap(spl_ir_builder_t *b);
|
||||
|
||||
spl_ir_node_ref_t spl_ir_builder_dbg_breakpoint(spl_ir_builder_t *b);
|
||||
spl_ir_node_ref_t spl_ir_builder_dbg_panic(spl_ir_builder_t *b, const char *msg, usize len);
|
||||
|
||||
#endif /* __SPL_IR_H__ */
|
||||
+1491
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,39 @@
|
||||
#ifndef __SPL_IR2VM_H__
|
||||
#define __SPL_IR2VM_H__
|
||||
|
||||
#include "spl_diag.h"
|
||||
#include "spl_ir.h"
|
||||
#include "spl_layout.h"
|
||||
#include "spl_type.h"
|
||||
|
||||
#include "../stage0/spl_mcode.h"
|
||||
|
||||
typedef struct {
|
||||
spl_ir_func_ref_t fid;
|
||||
VEC(usize) node_first_ip;
|
||||
} spl_ir2vm_fdbg_t;
|
||||
typedef VEC(spl_ir2vm_fdbg_t) spl_ir2vm_fdbg_vec_t;
|
||||
|
||||
typedef MAP(spl_ir_func_ref_t, isize) spl_extern_idx_map_t;
|
||||
|
||||
typedef struct {
|
||||
const spl_ir_t *ir;
|
||||
const spl_type_t *type;
|
||||
const spl_attr_t *attr;
|
||||
spl_extern_idx_map_t extern_native;
|
||||
spl_prog_t prog;
|
||||
spl_layout_t *layout; /* 借用 splc0 的 layout(size/align/offset 唯一来源) */
|
||||
|
||||
spl_diag_t *diag;
|
||||
int opt; /* 非 0:块内操作数栈化(表达式临时值留栈,块边界溢出回槽) */
|
||||
} spl_ir2vm_t;
|
||||
|
||||
void spl_ir2vm_init(spl_ir2vm_t *ctx, const spl_ir_t *ir, const spl_type_t *type,
|
||||
spl_layout_t *layout, const spl_attr_t *attr, int opt);
|
||||
void spl_ir2vm_drop(spl_ir2vm_t *ctx);
|
||||
|
||||
int spl_ir2vm_run_func(spl_ir2vm_t *ctx, spl_ir_func_ref_t ref);
|
||||
int spl_ir2vm_run(spl_ir2vm_t *ctx);
|
||||
void spl_ir2vm_dump(spl_ir2vm_t *ctx);
|
||||
|
||||
#endif /* __SPL_IR2VM_H__ */
|
||||
@@ -0,0 +1,836 @@
|
||||
#include "spl_lang_type.h"
|
||||
|
||||
static usize spl_lang_type_hash(spl_lang_type_node_t n) {
|
||||
usize hash = spl_hash_seed();
|
||||
hash = spl_hash_mix(hash, (usize)n.kind);
|
||||
hash = spl_hash_mix(hash, n.ir_type_id);
|
||||
switch (n.kind) {
|
||||
case SPL_LANG_TYPE_PTR:
|
||||
case SPL_LANG_TYPE_SLICE:
|
||||
case SPL_LANG_TYPE_RANGE:
|
||||
hash = spl_hash_mix(hash, n.extra.elem);
|
||||
break;
|
||||
case SPL_LANG_TYPE_TUPLE:
|
||||
hash = spl_hash_mix(hash, vec_size(n.extra.elems));
|
||||
for (usize i = 0; i < vec_size(n.extra.elems); i++) {
|
||||
hash = spl_hash_mix(hash, vec_at(n.extra.elems, i));
|
||||
}
|
||||
break;
|
||||
case SPL_LANG_TYPE_FN:
|
||||
hash = spl_hash_mix(hash, n.extra.fn.ret);
|
||||
hash = spl_hash_mix(hash, vec_size(n.extra.fn.params));
|
||||
for (usize i = 0; i < vec_size(n.extra.fn.params); i++) {
|
||||
hash = spl_hash_mix(hash, vec_at(n.extra.fn.params, i));
|
||||
}
|
||||
break;
|
||||
case SPL_LANG_TYPE_STRUCT:
|
||||
case SPL_LANG_TYPE_UNION:
|
||||
case SPL_LANG_TYPE_ENUM:
|
||||
case SPL_LANG_TYPE_SHAPE:
|
||||
hash = spl_hash_mix(hash, vec_size(n.methods));
|
||||
for (usize i = 0; i < vec_size(n.methods); i++) {
|
||||
spl_lang_method_t *m = &vec_at(n.methods, i);
|
||||
hash = spl_hash_mix(hash, m->def_id);
|
||||
hash = spl_hash_mix_str(hash, m->name);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int spl_lang_type_eq(spl_lang_type_node_t n1, spl_lang_type_node_t n2) {
|
||||
if (n1.kind != n2.kind || n1.ir_type_id != n2.ir_type_id)
|
||||
return 1;
|
||||
switch (n1.kind) {
|
||||
case SPL_LANG_TYPE_PTR:
|
||||
case SPL_LANG_TYPE_SLICE:
|
||||
case SPL_LANG_TYPE_RANGE:
|
||||
return n1.extra.elem != n2.extra.elem;
|
||||
case SPL_LANG_TYPE_TUPLE:
|
||||
if (vec_size(n1.extra.elems) != vec_size(n2.extra.elems))
|
||||
return 1;
|
||||
for (usize i = 0; i < vec_size(n1.extra.elems); i++) {
|
||||
if (vec_at(n1.extra.elems, i) != vec_at(n2.extra.elems, i))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
case SPL_LANG_TYPE_FN:
|
||||
if (n1.extra.fn.ret != n2.extra.fn.ret)
|
||||
return 1;
|
||||
if (vec_size(n1.extra.fn.params) != vec_size(n2.extra.fn.params))
|
||||
return 1;
|
||||
for (usize i = 0; i < vec_size(n1.extra.fn.params); i++) {
|
||||
if (vec_at(n1.extra.fn.params, i) != vec_at(n2.extra.fn.params, i))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
case SPL_LANG_TYPE_STRUCT:
|
||||
case SPL_LANG_TYPE_UNION:
|
||||
case SPL_LANG_TYPE_ENUM:
|
||||
case SPL_LANG_TYPE_SHAPE:
|
||||
if (vec_size(n1.methods) != vec_size(n2.methods))
|
||||
return 1;
|
||||
for (usize i = 0; i < vec_size(n1.methods); i++) {
|
||||
spl_lang_method_t *a = &vec_at(n1.methods, i);
|
||||
spl_lang_method_t *b = &vec_at(n2.methods, i);
|
||||
if (a->def_id != b->def_id)
|
||||
return 1;
|
||||
if ((a->name == NULL) != (b->name == NULL))
|
||||
return 1;
|
||||
if (a->name && strcmp(a->name, b->name) != 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void spl_lang_node_free_extra(spl_lang_type_node_t *n) {
|
||||
switch (n->kind) {
|
||||
case SPL_LANG_TYPE_TUPLE:
|
||||
vec_free(n->extra.elems);
|
||||
break;
|
||||
case SPL_LANG_TYPE_FN:
|
||||
vec_free(n->extra.fn.params);
|
||||
break;
|
||||
case SPL_LANG_TYPE_STRUCT:
|
||||
case SPL_LANG_TYPE_UNION:
|
||||
case SPL_LANG_TYPE_ENUM:
|
||||
case SPL_LANG_TYPE_SHAPE:
|
||||
vec_free(n->methods);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* spl_fold 回调:只在链内做结构比较(同桶已保证 hash 相同)。 */
|
||||
static int spl_lang_fold_eq(void *ctx, spl_fold_id_t node, const void *probe) {
|
||||
spl_lang_type_t *type = (spl_lang_type_t *)ctx;
|
||||
return spl_lang_type_eq(vec_at(type->lang_table, node),
|
||||
*(const spl_lang_type_node_t *)probe) == 0;
|
||||
}
|
||||
|
||||
static spl_fold_id_t spl_lang_fold_next(void *ctx, spl_fold_id_t node) {
|
||||
spl_lang_type_t *type = (spl_lang_type_t *)ctx;
|
||||
return vec_at(type->lang_table, node).hash_next;
|
||||
}
|
||||
|
||||
static void spl_lang_fold_set_next(void *ctx, spl_fold_id_t node, spl_fold_id_t next) {
|
||||
spl_lang_type_t *type = (spl_lang_type_t *)ctx;
|
||||
vec_at(type->lang_table, node).hash_next = next;
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_node_push(spl_lang_type_t *type, spl_lang_type_node_t *node) {
|
||||
usize hash = spl_lang_type_hash(*node);
|
||||
|
||||
spl_lang_type_id_t existing = spl_fold_find(&type->lang_fold, hash, node);
|
||||
if (existing) {
|
||||
spl_lang_node_free_extra(node);
|
||||
return existing;
|
||||
}
|
||||
|
||||
spl_lang_type_id_t id = vec_size(type->lang_table);
|
||||
vec_push(type->lang_table, *node);
|
||||
spl_fold_insert(&type->lang_fold, hash, id);
|
||||
node->kind = SPL_LANG_TYPE_ERROR;
|
||||
return id;
|
||||
}
|
||||
|
||||
void spl_lang_type_init(spl_lang_type_t *type) {
|
||||
spl_type_init(&type->ir);
|
||||
|
||||
vec_init(type->lang_table);
|
||||
spl_lang_type_node_t lang_node = {0};
|
||||
vec_push(type->lang_table, lang_node);
|
||||
spl_fold_init(&type->lang_fold, type, spl_lang_fold_eq, spl_lang_fold_next,
|
||||
spl_lang_fold_set_next);
|
||||
|
||||
vec_init(type->def_table);
|
||||
vec_push(type->def_table, (spl_def_node_t){0});
|
||||
}
|
||||
|
||||
void spl_lang_type_drop(spl_lang_type_t *type) {
|
||||
for (usize i = 0; i < vec_size(type->lang_table); i++) {
|
||||
spl_lang_type_node_t *n = &vec_at(type->lang_table, i);
|
||||
spl_lang_node_free_extra(n);
|
||||
}
|
||||
spl_fold_drop(&type->lang_fold);
|
||||
vec_free(type->lang_table);
|
||||
|
||||
vec_free(type->def_table);
|
||||
|
||||
spl_type_drop(&type->ir);
|
||||
}
|
||||
|
||||
spl_lang_type_node_t *spl_lang_type_node(const spl_lang_type_t *type, spl_lang_type_id_t id) {
|
||||
if (!id || id >= vec_size(type->lang_table))
|
||||
return NULL;
|
||||
return &vec_at(type->lang_table, id);
|
||||
}
|
||||
|
||||
spl_def_node_t *spl_def_node(const spl_lang_type_t *type, spl_def_id_t id) {
|
||||
if (!id || id >= vec_size(type->def_table))
|
||||
return NULL;
|
||||
return &vec_at(type->def_table, id);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_lang_type_get_ir_type(spl_lang_type_t *type, spl_lang_type_id_t lang_id) {
|
||||
spl_lang_type_node_t *n = spl_lang_type_node(type, lang_id);
|
||||
return n ? n->ir_type_id : 0;
|
||||
}
|
||||
|
||||
spl_type_id_t spl_lang_reserve_ir(spl_lang_type_t *type) {
|
||||
spl_type_node_t empty = {0};
|
||||
empty.kind = SPL_TYPE_ERROR;
|
||||
empty.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
vec_push(type->ir.type_table, empty);
|
||||
return vec_size(type->ir.type_table) - 1;
|
||||
}
|
||||
|
||||
void spl_lang_define_ir(spl_lang_type_t *type, spl_type_id_t id, spl_type_node_t *node) {
|
||||
if (!id || id >= vec_size(type->ir.type_table))
|
||||
return;
|
||||
spl_type_node_t *slot = &vec_at(type->ir.type_table, id);
|
||||
/* slot 原是空 ERROR 节点,无 vec 字段可释放。node 的 vec 字段 move 进 slot。
|
||||
占位 id 不写 type_fold,不参与 hash-consing。 */
|
||||
*slot = *node;
|
||||
node->kind = SPL_TYPE_ERROR;
|
||||
}
|
||||
|
||||
static spl_lang_type_id_t spl_lang_node_simple(spl_lang_type_t *type,
|
||||
spl_lang_type_kind_t lang_kind,
|
||||
spl_type_id_t ir_type_id) {
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = lang_kind;
|
||||
node.ir_type_id = ir_type_id;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_error(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_ERROR);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_ERROR, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_infer(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_INFER);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_INFER, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_never(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_NEVER);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_NEVER, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_any(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_ANY);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_ANY, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_unit(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_UNIT);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_UNIT, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_bool(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_BOOL);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_BOOL, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_int(spl_lang_type_t *type, usize bits, int is_signed) {
|
||||
spl_type_id_t ir = spl_type_builder_int(&type->ir, bits, is_signed);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_INT, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_float(spl_lang_type_t *type, usize bits) {
|
||||
spl_type_id_t ir = spl_type_builder_float(&type->ir, bits);
|
||||
return spl_lang_node_simple(type, SPL_LANG_TYPE_FLOAT, ir);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_array(spl_lang_type_t *type, spl_lang_type_id_t elem_lang,
|
||||
usize len) {
|
||||
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elem_lang);
|
||||
if (!elem_ir)
|
||||
return 0;
|
||||
spl_type_id_t ir = spl_type_builder_array(&type->ir, elem_ir, len);
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_ARRAY;
|
||||
node.ir_type_id = ir;
|
||||
node.extra.elem = elem_lang;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_ptr(spl_lang_type_t *type, spl_lang_type_id_t pointee_lang) {
|
||||
spl_type_id_t pointee_ir = spl_lang_type_get_ir_type(type, pointee_lang);
|
||||
if (!pointee_ir)
|
||||
return 0;
|
||||
spl_type_id_t ir = spl_type_builder_ptr(&type->ir, pointee_ir);
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_PTR;
|
||||
node.ir_type_id = ir;
|
||||
node.extra.elem = pointee_lang;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_slice(spl_lang_type_t *type, spl_lang_type_id_t elem_lang) {
|
||||
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elem_lang);
|
||||
if (!elem_ir)
|
||||
return 0;
|
||||
|
||||
spl_lang_type_id_t ptr_lang = spl_lang_make_ptr(type, elem_lang);
|
||||
spl_lang_type_id_t usize_lang = spl_lang_make_int(type, sizeof(void *) * 8, 0);
|
||||
spl_type_id_t ptr_ir = spl_lang_type_get_ir_type(type, ptr_lang);
|
||||
spl_type_id_t usize_ir = spl_lang_type_get_ir_type(type, usize_lang);
|
||||
|
||||
spl_type_member_vec_t members;
|
||||
vec_init(members);
|
||||
spl_type_member_t m1 = {.name = "ptr", .type_id = ptr_ir};
|
||||
spl_type_member_t m2 = {.name = "len", .type_id = usize_ir};
|
||||
vec_push(members, m1);
|
||||
vec_push(members, m2);
|
||||
|
||||
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
|
||||
spl_type_id_t struct_ir = spl_type_builder_agg(&type->ir, SPL_TYPE_STRUCT, &members, 0, layout);
|
||||
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_SLICE;
|
||||
node.ir_type_id = struct_ir;
|
||||
node.extra.elem = elem_lang;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_tuple(spl_lang_type_t *type, const spl_lang_type_id_t *elems,
|
||||
usize count) {
|
||||
spl_type_member_vec_t members;
|
||||
vec_init(members);
|
||||
for (usize i = 0; i < count; i++) {
|
||||
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elems[i]);
|
||||
if (!elem_ir) {
|
||||
vec_free(members);
|
||||
return 0;
|
||||
}
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "%zu", i);
|
||||
spl_type_member_t m = {.name = strdup(name), .type_id = elem_ir};
|
||||
vec_push(members, m);
|
||||
}
|
||||
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
|
||||
spl_type_id_t struct_ir = spl_type_builder_agg(&type->ir, SPL_TYPE_STRUCT, &members, 0, layout);
|
||||
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_TUPLE;
|
||||
node.ir_type_id = struct_ir;
|
||||
vec_init(node.extra.elems);
|
||||
for (usize i = 0; i < count; i++) {
|
||||
vec_push(node.extra.elems, elems[i]);
|
||||
}
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_range(spl_lang_type_t *type, spl_lang_type_id_t elem_lang) {
|
||||
spl_type_id_t elem_ir = spl_lang_type_get_ir_type(type, elem_lang);
|
||||
if (!elem_ir)
|
||||
return 0;
|
||||
|
||||
spl_type_member_vec_t members;
|
||||
vec_init(members);
|
||||
spl_type_member_t m1 = {.name = "start", .type_id = elem_ir};
|
||||
spl_type_member_t m2 = {.name = "end", .type_id = elem_ir};
|
||||
vec_push(members, m1);
|
||||
vec_push(members, m2);
|
||||
|
||||
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
|
||||
spl_type_id_t struct_ir = spl_type_builder_agg(&type->ir, SPL_TYPE_STRUCT, &members, 0, layout);
|
||||
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_RANGE;
|
||||
node.ir_type_id = struct_ir;
|
||||
node.extra.elem = elem_lang;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_fn(spl_lang_type_t *type, spl_lang_type_id_t ret_lang,
|
||||
const spl_lang_type_id_t *params_lang, usize nparams) {
|
||||
spl_type_id_t ret_ir = spl_lang_type_get_ir_type(type, ret_lang);
|
||||
if (!ret_ir)
|
||||
return 0;
|
||||
spl_type_id_vec_t param_irs;
|
||||
vec_init(param_irs);
|
||||
for (usize i = 0; i < nparams; i++) {
|
||||
spl_type_id_t pir = spl_lang_type_get_ir_type(type, params_lang[i]);
|
||||
if (!pir) {
|
||||
vec_free(param_irs);
|
||||
return 0;
|
||||
}
|
||||
vec_push(param_irs, pir);
|
||||
}
|
||||
spl_type_id_t fn_ir =
|
||||
spl_type_builder_fn(&type->ir, ret_ir, param_irs.data, vec_size(param_irs));
|
||||
vec_free(param_irs);
|
||||
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_FN;
|
||||
node.ir_type_id = fn_ir;
|
||||
node.extra.fn.ret = ret_lang;
|
||||
vec_init(node.extra.fn.params);
|
||||
for (usize i = 0; i < nparams; i++) {
|
||||
vec_push(node.extra.fn.params, params_lang[i]);
|
||||
}
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_aggregate(spl_lang_type_t *type, spl_lang_type_kind_t kind,
|
||||
const char *name, const spl_lang_type_id_t *members_lang,
|
||||
usize nmembers) {
|
||||
(void)name; // TODO
|
||||
|
||||
spl_type_node_kind_t tk;
|
||||
switch (kind) {
|
||||
case SPL_LANG_TYPE_STRUCT:
|
||||
tk = SPL_TYPE_STRUCT;
|
||||
break;
|
||||
case SPL_LANG_TYPE_UNION:
|
||||
tk = SPL_TYPE_UNION;
|
||||
break;
|
||||
case SPL_LANG_TYPE_ENUM:
|
||||
tk = SPL_TYPE_ENUM;
|
||||
break;
|
||||
case SPL_LANG_TYPE_SHAPE:
|
||||
tk = SPL_TYPE_SHAPE;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
spl_type_member_vec_t members;
|
||||
vec_init(members);
|
||||
for (usize i = 0; i < nmembers; i++) {
|
||||
spl_type_id_t member_ir = spl_lang_type_get_ir_type(type, members_lang[i]);
|
||||
if (!member_ir) {
|
||||
vec_free(members);
|
||||
return 0;
|
||||
}
|
||||
char field_name[32];
|
||||
snprintf(field_name, sizeof(field_name), "%zu", i);
|
||||
spl_type_member_t m = {.name = strdup(field_name), .type_id = member_ir};
|
||||
vec_push(members, m);
|
||||
}
|
||||
spl_type_layout_t layout = {.mode = SPL_TYPE_LAYOUT_AUTO, .fixed_align_bits = 0};
|
||||
spl_type_id_t agg_ir = spl_type_builder_agg(&type->ir, tk, &members, 0, layout);
|
||||
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = kind;
|
||||
node.ir_type_id = agg_ir;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_varargs(spl_lang_type_t *type) {
|
||||
spl_type_id_t ir = spl_type_builder_simple(&type->ir, SPL_TYPE_UNIT);
|
||||
spl_lang_type_node_t node = {0};
|
||||
node.kind = SPL_LANG_TYPE_VARARGS;
|
||||
node.ir_type_id = ir;
|
||||
return spl_lang_node_push(type, &node);
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_type_lookup_builtin(spl_lang_type_t *type, const char *name) {
|
||||
if (!name || !*name)
|
||||
return 0;
|
||||
if (strcmp(name, "bool") == 0)
|
||||
return spl_lang_make_bool(type);
|
||||
if (strcmp(name, "void") == 0)
|
||||
return spl_lang_make_unit(type);
|
||||
if (strcmp(name, "isize") == 0)
|
||||
return spl_lang_make_int(type, sizeof(void *) * 8, 1);
|
||||
if (strcmp(name, "usize") == 0)
|
||||
return spl_lang_make_int(type, sizeof(void *) * 8, 0);
|
||||
if (strcmp(name, "any") == 0)
|
||||
return spl_lang_make_any(type);
|
||||
char prefix = name[0];
|
||||
if (prefix == 'i' || prefix == 'u' || prefix == 'f') {
|
||||
const char *rest = name + 1;
|
||||
char *end;
|
||||
long bits = strtol(rest, &end, 10);
|
||||
if (end != rest && *end == '\0' && bits > 0) {
|
||||
if (prefix == 'i')
|
||||
return spl_lang_make_int(type, (usize)bits, 1);
|
||||
if (prefix == 'u')
|
||||
return spl_lang_make_int(type, (usize)bits, 0);
|
||||
if (prefix == 'f')
|
||||
return spl_lang_make_float(type, (usize)bits);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool spl_lang_type_is_subtype(const spl_lang_type_t *type, spl_lang_type_id_t sub,
|
||||
spl_lang_type_id_t super) {
|
||||
if (sub == 0 || super == 0)
|
||||
return false;
|
||||
if (sub == super)
|
||||
return true;
|
||||
|
||||
spl_lang_type_node_t *nsub = spl_lang_type_node(type, sub);
|
||||
spl_lang_type_node_t *nsuper = spl_lang_type_node(type, super);
|
||||
if (!nsub || !nsuper)
|
||||
return false;
|
||||
|
||||
if (nsub->kind == SPL_LANG_TYPE_NEVER)
|
||||
return true;
|
||||
if (nsuper->kind == SPL_LANG_TYPE_ANY)
|
||||
return true;
|
||||
|
||||
if (nsub->kind == SPL_LANG_TYPE_INT && nsuper->kind == SPL_LANG_TYPE_INT) {
|
||||
spl_type_node_t *ir_sub = spl_type_node(&type->ir, nsub->ir_type_id);
|
||||
if (!ir_sub)
|
||||
return false;
|
||||
return ir_sub->int_type.bits == 0;
|
||||
}
|
||||
|
||||
if (nsub->kind == SPL_LANG_TYPE_FLOAT && nsuper->kind == SPL_LANG_TYPE_FLOAT) {
|
||||
spl_type_node_t *ir_sub = spl_type_node(&type->ir, nsub->ir_type_id);
|
||||
if (!ir_sub)
|
||||
return false;
|
||||
return ir_sub->float_type.bits == 0;
|
||||
}
|
||||
|
||||
/* null(*never)可隐式转换为函数指针:函数值按指针布局,可空。 */
|
||||
if (nsuper->kind == SPL_LANG_TYPE_FN && nsub->kind == SPL_LANG_TYPE_PTR) {
|
||||
spl_lang_type_node_t *sub_elem = spl_lang_type_node(type, nsub->extra.elem);
|
||||
if (sub_elem && sub_elem->kind == SPL_LANG_TYPE_NEVER)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nsub->kind == SPL_LANG_TYPE_PTR && nsuper->kind == SPL_LANG_TYPE_PTR) {
|
||||
if (nsub->extra.elem == nsuper->extra.elem)
|
||||
return true;
|
||||
spl_lang_type_node_t *sub_elem = spl_lang_type_node(type, nsub->extra.elem);
|
||||
spl_lang_type_node_t *super_elem = spl_lang_type_node(type, nsuper->extra.elem);
|
||||
if (sub_elem && sub_elem->kind == SPL_LANG_TYPE_NEVER)
|
||||
return true;
|
||||
if (super_elem && super_elem->kind == SPL_LANG_TYPE_ANY)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_lang_type_join(const spl_lang_type_t *type, spl_lang_type_id_t a,
|
||||
spl_lang_type_id_t b) {
|
||||
if (a == 0 || b == 0)
|
||||
return 0;
|
||||
if (a == b)
|
||||
return a;
|
||||
|
||||
spl_lang_type_node_t *na = spl_lang_type_node(type, a);
|
||||
spl_lang_type_node_t *nb = spl_lang_type_node(type, b);
|
||||
if (!na || !nb)
|
||||
return 0;
|
||||
|
||||
if (na->kind == SPL_LANG_TYPE_NEVER)
|
||||
return b;
|
||||
if (nb->kind == SPL_LANG_TYPE_NEVER)
|
||||
return a;
|
||||
if (na->kind == SPL_LANG_TYPE_ANY || nb->kind == SPL_LANG_TYPE_ANY)
|
||||
return spl_lang_make_any((spl_lang_type_t *)type);
|
||||
|
||||
bool a_int = na->kind == SPL_LANG_TYPE_INT;
|
||||
bool b_int = nb->kind == SPL_LANG_TYPE_INT;
|
||||
bool a_flt = na->kind == SPL_LANG_TYPE_FLOAT;
|
||||
bool b_flt = nb->kind == SPL_LANG_TYPE_FLOAT;
|
||||
|
||||
if (a_int && b_int) {
|
||||
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
|
||||
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
|
||||
if (!ira || !irb)
|
||||
return 0;
|
||||
bool a_ct = ira->int_type.bits == 0;
|
||||
bool b_ct = irb->int_type.bits == 0;
|
||||
if (a_ct && b_ct)
|
||||
return a;
|
||||
if (a_ct)
|
||||
return b;
|
||||
if (b_ct)
|
||||
return a;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a_flt && b_flt) {
|
||||
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
|
||||
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
|
||||
if (!ira || !irb)
|
||||
return 0;
|
||||
bool a_ct = ira->float_type.bits == 0;
|
||||
bool b_ct = irb->float_type.bits == 0;
|
||||
if (a_ct && b_ct)
|
||||
return a;
|
||||
if (a_ct)
|
||||
return b;
|
||||
if (b_ct)
|
||||
return a;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a_int && b_flt) {
|
||||
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
|
||||
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
|
||||
if (ira && irb && ira->int_type.bits == 0 && irb->float_type.bits == 0)
|
||||
return spl_lang_make_float((spl_lang_type_t *)type, 0);
|
||||
return 0;
|
||||
}
|
||||
if (a_flt && b_int) {
|
||||
spl_type_node_t *ira = spl_type_node(&type->ir, na->ir_type_id);
|
||||
spl_type_node_t *irb = spl_type_node(&type->ir, nb->ir_type_id);
|
||||
if (ira && irb && ira->float_type.bits == 0 && irb->int_type.bits == 0)
|
||||
return spl_lang_make_float((spl_lang_type_t *)type, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 函数指针可空:fn 与 null(*never)求交得到 fn。 */
|
||||
if (na->kind == SPL_LANG_TYPE_FN && nb->kind == SPL_LANG_TYPE_PTR) {
|
||||
spl_lang_type_node_t *eb = spl_lang_type_node(type, nb->extra.elem);
|
||||
if (eb && eb->kind == SPL_LANG_TYPE_NEVER)
|
||||
return a;
|
||||
}
|
||||
if (na->kind == SPL_LANG_TYPE_PTR && nb->kind == SPL_LANG_TYPE_FN) {
|
||||
spl_lang_type_node_t *ea = spl_lang_type_node(type, na->extra.elem);
|
||||
if (ea && ea->kind == SPL_LANG_TYPE_NEVER)
|
||||
return b;
|
||||
}
|
||||
|
||||
if (na->kind == SPL_LANG_TYPE_PTR && nb->kind == SPL_LANG_TYPE_PTR) {
|
||||
if (na->extra.elem == nb->extra.elem)
|
||||
return a;
|
||||
|
||||
spl_lang_type_node_t *ea = spl_lang_type_node(type, na->extra.elem);
|
||||
spl_lang_type_node_t *eb = spl_lang_type_node(type, nb->extra.elem);
|
||||
|
||||
if (ea && ea->kind == SPL_LANG_TYPE_NEVER)
|
||||
return b;
|
||||
if (eb && eb->kind == SPL_LANG_TYPE_NEVER)
|
||||
return a;
|
||||
if (ea && ea->kind == SPL_LANG_TYPE_ANY)
|
||||
return a;
|
||||
if (eb && eb->kind == SPL_LANG_TYPE_ANY)
|
||||
return b;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool spl_lang_mpa_int_fits(const spl_lang_type_t *type, spl_lang_type_id_t target,
|
||||
spl_mpa_int_t v) {
|
||||
spl_lang_type_node_t *nt = spl_lang_type_node(type, target);
|
||||
if (!nt || nt->kind != SPL_LANG_TYPE_INT)
|
||||
return false;
|
||||
spl_type_node_t *ir = spl_type_node(&type->ir, nt->ir_type_id);
|
||||
if (!ir)
|
||||
return false;
|
||||
uint32_t bits = (uint32_t)ir->int_type.bits;
|
||||
if (bits == 0)
|
||||
return true;
|
||||
bool target_signed = ir->int_type.is_signed != 0;
|
||||
uint64_t uval = v.bits;
|
||||
|
||||
if (target_signed) {
|
||||
if (bits >= 64)
|
||||
return true;
|
||||
uint64_t hi = ((uint64_t)1 << (bits - 1)) - 1;
|
||||
return uval <= hi;
|
||||
}
|
||||
if (bits >= 64)
|
||||
return true;
|
||||
uint64_t hi = ((uint64_t)1 << bits) - 1;
|
||||
return uval <= hi;
|
||||
}
|
||||
|
||||
spl_def_id_t spl_def_alloc(spl_lang_type_t *type, spl_def_node_kind_t kind, const char *name,
|
||||
spl_dbg_node_t dbg, struct spl_ast *owner_ast, usize ast_node_ref) {
|
||||
spl_def_node_t n = {0};
|
||||
n.kind = kind;
|
||||
n.name = name;
|
||||
n.dbg_node = dbg;
|
||||
n.lang_id = 0;
|
||||
n.owner_ast = owner_ast;
|
||||
n.ast_node_ref = ast_node_ref;
|
||||
n.state = SPL_DECL_UNRESOLVED;
|
||||
n.owner_def = 0;
|
||||
vec_push(type->def_table, n);
|
||||
return vec_size(type->def_table) - 1;
|
||||
}
|
||||
|
||||
void spl_def_resolve(spl_lang_type_t *type, spl_def_id_t id, spl_lang_type_id_t lang_id) {
|
||||
spl_def_node_t *d = spl_def_node(type, id);
|
||||
if (d) {
|
||||
d->lang_id = lang_id;
|
||||
d->state = SPL_DECL_RESOLVED;
|
||||
}
|
||||
}
|
||||
|
||||
spl_lang_type_id_t spl_def_get_lang_id(spl_lang_type_t *type, spl_def_id_t def_id) {
|
||||
spl_def_node_t *d = spl_def_node(type, def_id);
|
||||
return d ? d->lang_id : 0;
|
||||
}
|
||||
|
||||
spl_type_id_t spl_def_get_type_id(spl_lang_type_t *type, spl_def_id_t def_id) {
|
||||
spl_lang_type_id_t lang = spl_def_get_lang_id(type, def_id);
|
||||
return lang ? spl_lang_type_get_ir_type(type, lang) : 0;
|
||||
}
|
||||
|
||||
static void spl_lang_type_dump_rec(spl_lang_type_t *type, spl_lang_type_id_t id,
|
||||
spl_lang_type_id_vec_t *stack) {
|
||||
for (usize i = 0; i < vec_size(*stack); i++) {
|
||||
if (vec_at(*stack, i) == id) {
|
||||
printf("<rec#%zu>", id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (vec_size(*stack) > 32) {
|
||||
printf("...");
|
||||
return;
|
||||
}
|
||||
vec_push(*stack, id);
|
||||
|
||||
spl_lang_type_node_t *n = spl_lang_type_node(type, id);
|
||||
if (!n) {
|
||||
printf("none");
|
||||
(void)vec_pop(*stack);
|
||||
return;
|
||||
}
|
||||
switch (n->kind) {
|
||||
case SPL_LANG_TYPE_ERROR:
|
||||
printf("error");
|
||||
break;
|
||||
case SPL_LANG_TYPE_INFER:
|
||||
printf("infer");
|
||||
break;
|
||||
case SPL_LANG_TYPE_NEVER:
|
||||
printf("never");
|
||||
break;
|
||||
case SPL_LANG_TYPE_ANY:
|
||||
printf("any");
|
||||
break;
|
||||
case SPL_LANG_TYPE_UNIT:
|
||||
printf("unit");
|
||||
break;
|
||||
case SPL_LANG_TYPE_BOOL:
|
||||
printf("bool");
|
||||
break;
|
||||
case SPL_LANG_TYPE_VARARGS:
|
||||
printf("...");
|
||||
break;
|
||||
case SPL_LANG_TYPE_INT: {
|
||||
spl_type_node_t *tn = spl_type_node(&type->ir, n->ir_type_id);
|
||||
if (tn && tn->int_type.bits == 0)
|
||||
printf("comptime_int");
|
||||
else
|
||||
spl_type_pure_dump(&type->ir, n->ir_type_id);
|
||||
break;
|
||||
}
|
||||
case SPL_LANG_TYPE_FLOAT: {
|
||||
spl_type_node_t *tn = spl_type_node(&type->ir, n->ir_type_id);
|
||||
if (tn && tn->float_type.bits == 0)
|
||||
printf("comptime_float");
|
||||
else
|
||||
spl_type_pure_dump(&type->ir, n->ir_type_id);
|
||||
break;
|
||||
}
|
||||
case SPL_LANG_TYPE_PTR:
|
||||
printf("*");
|
||||
spl_lang_type_dump_rec(type, n->extra.elem, stack);
|
||||
break;
|
||||
case SPL_LANG_TYPE_ARRAY: {
|
||||
spl_type_node_t *tn = spl_type_node(&type->ir, n->ir_type_id);
|
||||
printf("[%zu]", tn->array_type.len);
|
||||
spl_type_pure_dump(&type->ir, n->ir_type_id);
|
||||
} break;
|
||||
case SPL_LANG_TYPE_SLICE:
|
||||
printf("[]");
|
||||
spl_lang_type_dump_rec(type, n->extra.elem, stack);
|
||||
break;
|
||||
case SPL_LANG_TYPE_TUPLE:
|
||||
printf("(");
|
||||
for (usize i = 0; i < vec_size(n->extra.elems); i++) {
|
||||
if (i)
|
||||
printf(",");
|
||||
spl_lang_type_dump_rec(type, vec_at(n->extra.elems, i), stack);
|
||||
}
|
||||
printf(")");
|
||||
break;
|
||||
case SPL_LANG_TYPE_RANGE:
|
||||
spl_lang_type_dump_rec(type, n->extra.elem, stack);
|
||||
printf("..");
|
||||
spl_lang_type_dump_rec(type, n->extra.elem, stack);
|
||||
break;
|
||||
case SPL_LANG_TYPE_FN:
|
||||
printf("fn(");
|
||||
for (usize i = 0; i < vec_size(n->extra.fn.params); i++) {
|
||||
if (i)
|
||||
printf(",");
|
||||
spl_lang_type_dump_rec(type, vec_at(n->extra.fn.params, i), stack);
|
||||
}
|
||||
printf(")");
|
||||
spl_lang_type_dump_rec(type, n->extra.fn.ret, stack);
|
||||
break;
|
||||
case SPL_LANG_TYPE_STRUCT:
|
||||
case SPL_LANG_TYPE_UNION:
|
||||
case SPL_LANG_TYPE_ENUM:
|
||||
case SPL_LANG_TYPE_SHAPE:
|
||||
spl_type_pure_dump(&type->ir, n->ir_type_id);
|
||||
break;
|
||||
}
|
||||
|
||||
(void)vec_pop(*stack);
|
||||
}
|
||||
|
||||
void spl_lang_type_dump(spl_lang_type_t *type, spl_lang_type_id_t id) {
|
||||
spl_lang_type_id_vec_t stack;
|
||||
vec_init(stack);
|
||||
spl_lang_type_dump_rec(type, id, &stack);
|
||||
vec_free(stack);
|
||||
}
|
||||
|
||||
void spl_type_def_dump(spl_lang_type_t *type, spl_def_id_t id) {
|
||||
spl_def_node_t *node = spl_def_node(type, id);
|
||||
if (!node) {
|
||||
printf("none");
|
||||
return;
|
||||
}
|
||||
const char *def_kind_name = "unknown";
|
||||
switch (node->kind) {
|
||||
case SPL_DEF_ERROR:
|
||||
def_kind_name = "error";
|
||||
break;
|
||||
case SPL_DEF_SCALAR:
|
||||
def_kind_name = "scalar";
|
||||
break;
|
||||
case SPL_DEF_MEMBER:
|
||||
def_kind_name = "member";
|
||||
break;
|
||||
case SPL_DEF_VAR:
|
||||
def_kind_name = "var";
|
||||
break;
|
||||
case SPL_DEF_FN:
|
||||
def_kind_name = "fn";
|
||||
break;
|
||||
case SPL_DEF_AGG:
|
||||
def_kind_name = "agg";
|
||||
break;
|
||||
case SPL_DEF_BUILTIN:
|
||||
def_kind_name = "builtin";
|
||||
break;
|
||||
case SPL_DEF_DISTINCT:
|
||||
def_kind_name = "distinct";
|
||||
break;
|
||||
case SPL_DEF_ALIAS:
|
||||
def_kind_name = "alias";
|
||||
break;
|
||||
}
|
||||
printf("kind=%s lang_id=%zu name=%s", def_kind_name, node->lang_id,
|
||||
node->name ? node->name : "?");
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
#ifndef __SPL_LANG_TYPE_H__
|
||||
#define __SPL_LANG_TYPE_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_dbg.h"
|
||||
#include "spl_mpa.h"
|
||||
#include "spl_type.h"
|
||||
|
||||
typedef usize spl_lang_type_id_t;
|
||||
typedef VEC(spl_lang_type_id_t) spl_lang_type_id_vec_t;
|
||||
typedef usize spl_def_id_t;
|
||||
typedef VEC(spl_def_id_t) spl_def_id_vec_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_LANG_TYPE_ERROR,
|
||||
SPL_LANG_TYPE_INFER,
|
||||
SPL_LANG_TYPE_NEVER,
|
||||
SPL_LANG_TYPE_ANY,
|
||||
SPL_LANG_TYPE_UNIT,
|
||||
SPL_LANG_TYPE_BOOL,
|
||||
SPL_LANG_TYPE_INT,
|
||||
SPL_LANG_TYPE_FLOAT,
|
||||
SPL_LANG_TYPE_PTR,
|
||||
SPL_LANG_TYPE_ARRAY,
|
||||
SPL_LANG_TYPE_SLICE,
|
||||
SPL_LANG_TYPE_TUPLE,
|
||||
SPL_LANG_TYPE_RANGE,
|
||||
SPL_LANG_TYPE_FN,
|
||||
SPL_LANG_TYPE_STRUCT,
|
||||
SPL_LANG_TYPE_UNION,
|
||||
SPL_LANG_TYPE_ENUM,
|
||||
SPL_LANG_TYPE_SHAPE,
|
||||
SPL_LANG_TYPE_VARARGS,
|
||||
} spl_lang_type_kind_t;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
spl_def_id_t def_id;
|
||||
} spl_lang_method_t;
|
||||
typedef VEC(spl_lang_method_t) spl_lang_method_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_lang_type_kind_t kind;
|
||||
spl_type_id_t ir_type_id;
|
||||
spl_lang_method_vec_t methods;
|
||||
union {
|
||||
spl_lang_type_id_t elem;
|
||||
spl_lang_type_id_vec_t elems;
|
||||
struct {
|
||||
spl_lang_type_id_vec_t params;
|
||||
spl_lang_type_id_t ret;
|
||||
} fn;
|
||||
} extra;
|
||||
usize hash_next; /* 同 hash 桶内下一个类型 id;0 表示链尾。不参与相等比较。 */
|
||||
} spl_lang_type_node_t;
|
||||
typedef VEC(spl_lang_type_node_t) spl_lang_type_vec_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_DEF_ERROR,
|
||||
SPL_DEF_SCALAR,
|
||||
SPL_DEF_MEMBER,
|
||||
SPL_DEF_VAR,
|
||||
SPL_DEF_FN,
|
||||
SPL_DEF_AGG,
|
||||
SPL_DEF_DISTINCT,
|
||||
SPL_DEF_ALIAS,
|
||||
SPL_DEF_BUILTIN,
|
||||
} spl_def_node_kind_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_DECL_UNRESOLVED,
|
||||
SPL_DECL_RESOLVING,
|
||||
SPL_DECL_RESOLVED,
|
||||
SPL_DECL_ERROR,
|
||||
} spl_decl_state_t;
|
||||
|
||||
struct spl_ast;
|
||||
|
||||
typedef struct {
|
||||
spl_def_node_kind_t kind;
|
||||
const char *name;
|
||||
spl_dbg_node_t dbg_node;
|
||||
spl_lang_type_id_t lang_id;
|
||||
struct spl_ast *owner_ast;
|
||||
usize ast_node_ref;
|
||||
spl_decl_state_t state;
|
||||
spl_def_id_t owner_def;
|
||||
} spl_def_node_t;
|
||||
typedef VEC(spl_def_node_t) spl_def_node_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_type_t ir;
|
||||
spl_lang_type_vec_t lang_table;
|
||||
spl_fold_t lang_fold;
|
||||
spl_def_node_vec_t def_table;
|
||||
} spl_lang_type_t;
|
||||
|
||||
void spl_lang_type_init(spl_lang_type_t *type);
|
||||
void spl_lang_type_drop(spl_lang_type_t *type);
|
||||
|
||||
spl_lang_type_node_t *spl_lang_type_node(const spl_lang_type_t *type, spl_lang_type_id_t id);
|
||||
spl_lang_type_id_t spl_lang_node_push(spl_lang_type_t *type, spl_lang_type_node_t *node);
|
||||
|
||||
spl_type_id_t spl_lang_type_get_ir_type(spl_lang_type_t *type, spl_lang_type_id_t lang_id);
|
||||
|
||||
/* 前向引用支持:reserve 一个 type_table 空槽位(kind=ERROR)作为身份锚点,
|
||||
让 *Node / []Node 这类成员能拿到稳定 pointee;成员解析完后再调 define
|
||||
把真实内容填进去。占位槽位**不写 type_fold**,不参与 hash-consing——
|
||||
后续相同形状的类型走 spl_type_node_push 会生成独立 id,语义等价。 */
|
||||
spl_type_id_t spl_lang_reserve_ir(spl_lang_type_t *type);
|
||||
void spl_lang_define_ir(spl_lang_type_t *type, spl_type_id_t id, spl_type_node_t *node);
|
||||
|
||||
spl_lang_type_id_t spl_lang_make_error(spl_lang_type_t *type);
|
||||
spl_lang_type_id_t spl_lang_make_infer(spl_lang_type_t *type);
|
||||
spl_lang_type_id_t spl_lang_make_never(spl_lang_type_t *type);
|
||||
spl_lang_type_id_t spl_lang_make_any(spl_lang_type_t *type);
|
||||
spl_lang_type_id_t spl_lang_make_unit(spl_lang_type_t *type);
|
||||
spl_lang_type_id_t spl_lang_make_bool(spl_lang_type_t *type);
|
||||
spl_lang_type_id_t spl_lang_make_int(spl_lang_type_t *type, usize bits, int is_signed);
|
||||
spl_lang_type_id_t spl_lang_make_float(spl_lang_type_t *type, usize bits);
|
||||
spl_lang_type_id_t spl_lang_make_ptr(spl_lang_type_t *type, spl_lang_type_id_t pointee);
|
||||
spl_lang_type_id_t spl_lang_make_array(spl_lang_type_t *type, spl_lang_type_id_t elem, usize len);
|
||||
spl_lang_type_id_t spl_lang_make_slice(spl_lang_type_t *type, spl_lang_type_id_t elem);
|
||||
spl_lang_type_id_t spl_lang_make_tuple(spl_lang_type_t *type, const spl_lang_type_id_t *elems,
|
||||
usize count);
|
||||
spl_lang_type_id_t spl_lang_make_range(spl_lang_type_t *type, spl_lang_type_id_t elem);
|
||||
spl_lang_type_id_t spl_lang_make_fn(spl_lang_type_t *type, spl_lang_type_id_t ret,
|
||||
const spl_lang_type_id_t *params, usize nparams);
|
||||
spl_lang_type_id_t spl_lang_make_aggregate(spl_lang_type_t *type, spl_lang_type_kind_t kind,
|
||||
const char *name, const spl_lang_type_id_t *members,
|
||||
usize nmembers);
|
||||
spl_lang_type_id_t spl_lang_make_varargs(spl_lang_type_t *type);
|
||||
|
||||
spl_lang_type_id_t spl_type_lookup_builtin(spl_lang_type_t *type, const char *name);
|
||||
|
||||
bool spl_lang_type_is_subtype(const spl_lang_type_t *type, spl_lang_type_id_t sub,
|
||||
spl_lang_type_id_t super);
|
||||
spl_lang_type_id_t spl_lang_type_join(const spl_lang_type_t *type, spl_lang_type_id_t a,
|
||||
spl_lang_type_id_t b);
|
||||
|
||||
bool spl_lang_mpa_int_fits(const spl_lang_type_t *type, spl_lang_type_id_t target, spl_mpa_int_t v);
|
||||
|
||||
spl_def_id_t spl_def_alloc(spl_lang_type_t *type, spl_def_node_kind_t kind, const char *name,
|
||||
spl_dbg_node_t dbg, struct spl_ast *owner_ast, usize ast_node_ref);
|
||||
void spl_def_resolve(spl_lang_type_t *type, spl_def_id_t id, spl_lang_type_id_t lang_id);
|
||||
spl_def_node_t *spl_def_node(const spl_lang_type_t *type, spl_def_id_t id);
|
||||
spl_lang_type_id_t spl_def_get_lang_id(spl_lang_type_t *type, spl_def_id_t def_id);
|
||||
spl_type_id_t spl_def_get_type_id(spl_lang_type_t *type, spl_def_id_t def_id);
|
||||
|
||||
void spl_lang_type_dump(spl_lang_type_t *type, spl_lang_type_id_t id);
|
||||
void spl_type_def_dump(spl_lang_type_t *type, spl_def_id_t id);
|
||||
|
||||
#endif /* __SPL_LANG_TYPE_H__ */
|
||||
@@ -0,0 +1,324 @@
|
||||
#include "spl_layout.h"
|
||||
|
||||
static void infos_grow(spl_layout_t *layout, usize n) {
|
||||
while (vec_size(layout->infos) < n) {
|
||||
spl_layout_info_t info = {0};
|
||||
vec_init(info.field_bit_offsets);
|
||||
vec_init(info.field_bit_sizes);
|
||||
vec_push(layout->infos, info);
|
||||
}
|
||||
}
|
||||
|
||||
static spl_layout_info_t *infos_at(spl_layout_t *layout, spl_type_id_t id) {
|
||||
if (id == 0 || id >= vec_size(layout->infos))
|
||||
return NULL;
|
||||
return &vec_at(layout->infos, id);
|
||||
}
|
||||
|
||||
void spl_layout_add_scalar(spl_layout_t *layout, usize size, usize abi_align) {
|
||||
spl_layout_scalar_t s = {0};
|
||||
s.size = size;
|
||||
s.abi_align = abi_align;
|
||||
s.pref_align = abi_align;
|
||||
vec_init(s.invalid_values);
|
||||
vec_push(layout->scalars, s);
|
||||
}
|
||||
|
||||
void spl_layout_add_ptr(spl_layout_t *layout, usize addr_space, usize size, usize align) {
|
||||
spl_layout_ptr_t p = {0};
|
||||
p.add_space = addr_space;
|
||||
p.ptr_size = size;
|
||||
p.ptr_align = align;
|
||||
vec_push(layout->pointers, p);
|
||||
}
|
||||
|
||||
void spl_layout_init(spl_layout_t *layout, spl_layout_target_t *target) {
|
||||
vec_init(layout->scalars);
|
||||
vec_init(layout->pointers);
|
||||
vec_init(layout->infos);
|
||||
layout->target = target;
|
||||
|
||||
/* 默认聚合策略:按最大字段对齐,不允许重排 */
|
||||
layout->aggregate.allow_field_reordering = false;
|
||||
layout->aggregate.align_rule = ALIGN_MAX;
|
||||
layout->aggregate.fixed_align = 0;
|
||||
|
||||
/* 常见 64 位平台的默认布局。
|
||||
若目标平台不适用,调用者 clear 后自行 add_*。 */
|
||||
spl_layout_add_scalar(layout, 1, 1); /* i8 / u8 / bool */
|
||||
spl_layout_add_scalar(layout, 2, 2); /* i16 / u16 */
|
||||
spl_layout_add_scalar(layout, 4, 4); /* i32 / u32 / f32 */
|
||||
spl_layout_add_scalar(layout, 8, 8); /* i64 / u64 / f64 */
|
||||
|
||||
usize ps = target && target->ptr_size ? target->ptr_size : sizeof(void *);
|
||||
usize pa = target && target->ptr_align ? target->ptr_align : sizeof(void *);
|
||||
spl_layout_add_ptr(layout, 0, ps, pa);
|
||||
}
|
||||
|
||||
void spl_layout_drop(spl_layout_t *layout) {
|
||||
for (usize i = 0; i < vec_size(layout->scalars); ++i)
|
||||
vec_free(vec_at(layout->scalars, i).invalid_values);
|
||||
vec_free(layout->scalars);
|
||||
|
||||
vec_free(layout->pointers);
|
||||
|
||||
for (usize i = 0; i < vec_size(layout->infos); ++i) {
|
||||
vec_free(vec_at(layout->infos, i).field_bit_offsets);
|
||||
vec_free(vec_at(layout->infos, i).field_bit_sizes);
|
||||
}
|
||||
vec_free(layout->infos);
|
||||
}
|
||||
|
||||
static const spl_layout_scalar_t *scalar_for_size(spl_layout_t *layout, usize size_bytes) {
|
||||
for (usize i = 0; i < vec_size(layout->scalars); ++i) {
|
||||
spl_layout_scalar_t *s = &vec_at(layout->scalars, i);
|
||||
if (s->size == size_bytes)
|
||||
return s;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const spl_layout_ptr_t *default_ptr(spl_layout_t *layout) {
|
||||
if (vec_size(layout->pointers) == 0)
|
||||
return NULL;
|
||||
return &vec_at(layout->pointers, 0);
|
||||
}
|
||||
|
||||
static bool layout_scalar(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_info_t *out) {
|
||||
usize size_bits = 0;
|
||||
switch (tn->kind) {
|
||||
case SPL_TYPE_INT:
|
||||
if (tn->int_type.bits == 0)
|
||||
return false; /* comptime_int 未落地 */
|
||||
size_bits = tn->int_type.bits;
|
||||
break;
|
||||
case SPL_TYPE_FLOAT:
|
||||
if (tn->float_type.bits == 0)
|
||||
return false; /* comptime_float 未落地 */
|
||||
size_bits = tn->float_type.bits;
|
||||
break;
|
||||
case SPL_TYPE_BOOL:
|
||||
size_bits = 8;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
usize size_bytes = (size_bits + 7) / 8;
|
||||
const spl_layout_scalar_t *sl = scalar_for_size(layout, size_bytes);
|
||||
if (!sl)
|
||||
return false;
|
||||
|
||||
out->size_bits = sl->size * 8;
|
||||
out->align_bits = sl->abi_align * 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool layout_ptr(spl_layout_t *layout, spl_layout_info_t *out) {
|
||||
const spl_layout_ptr_t *pl = default_ptr(layout);
|
||||
if (!pl)
|
||||
return false;
|
||||
out->size_bits = pl->ptr_size * 8;
|
||||
out->align_bits = pl->ptr_align * 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
static usize round_up_bits(usize x, usize align_bits) {
|
||||
if (align_bits <= 1)
|
||||
return x;
|
||||
usize a = align_bits;
|
||||
return (x + a - 1) / a * a;
|
||||
}
|
||||
|
||||
static bool layout_agg(spl_layout_t *layout, spl_type_node_t *tn, spl_layout_info_t *out) {
|
||||
usize n = vec_size(tn->agg_type.variants);
|
||||
|
||||
if (tn->kind == SPL_TYPE_ENUM) {
|
||||
spl_type_id_t tag_tid = tn->agg_type.tag_type;
|
||||
if (!tag_tid)
|
||||
return false;
|
||||
spl_layout_info_t *tag_info = infos_at(layout, tag_tid);
|
||||
if (!tag_info || !tag_info->resolved)
|
||||
return false;
|
||||
|
||||
usize tag_align_bits = tag_info->align_bits ? tag_info->align_bits : 8;
|
||||
usize payload_align_bits = 8;
|
||||
usize payload_size_bits = 0;
|
||||
|
||||
for (usize i = 0; i < n; ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
if (m->type_id == 0) /* 无载荷变体 */
|
||||
continue;
|
||||
spl_layout_info_t *fi = infos_at(layout, m->type_id);
|
||||
if (!fi || !fi->resolved)
|
||||
return false;
|
||||
usize fa = fi->align_bits ? fi->align_bits : 8;
|
||||
if (fa > payload_align_bits)
|
||||
payload_align_bits = fa;
|
||||
if (fi->size_bits > payload_size_bits)
|
||||
payload_size_bits = fi->size_bits;
|
||||
}
|
||||
|
||||
usize payload_off = round_up_bits(tag_info->size_bits, payload_align_bits);
|
||||
usize final_align_bits =
|
||||
tag_align_bits > payload_align_bits ? tag_align_bits : payload_align_bits;
|
||||
out->size_bits = round_up_bits(payload_off + payload_size_bits, final_align_bits);
|
||||
out->align_bits = final_align_bits;
|
||||
|
||||
/* 字段索引约定(与 ir2vm 一致):0 = tag,1..n = 各变体载荷(同一偏移)。 */
|
||||
vec_push(out->field_bit_offsets, 0);
|
||||
vec_push(out->field_bit_sizes, tag_info->size_bits);
|
||||
for (usize i = 0; i < n; ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
usize fsize = 0;
|
||||
if (m->type_id) {
|
||||
spl_layout_info_t *fi = infos_at(layout, m->type_id);
|
||||
if (fi)
|
||||
fsize = fi->size_bits;
|
||||
}
|
||||
vec_push(out->field_bit_offsets, payload_off);
|
||||
vec_push(out->field_bit_sizes, fsize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
out->size_bits = 0;
|
||||
out->align_bits = 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
usize max_align_bits = 8;
|
||||
usize offset_bits = 0;
|
||||
bool is_union = (tn->kind == SPL_TYPE_UNION);
|
||||
|
||||
for (usize i = 0; i < n; ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
usize fsize_bits = 0;
|
||||
usize fsize_align = 8;
|
||||
if (m->type_id) {
|
||||
spl_layout_info_t *fi = infos_at(layout, m->type_id);
|
||||
if (!fi || !fi->resolved)
|
||||
return false;
|
||||
fsize_bits = fi->size_bits;
|
||||
fsize_align = fi->align_bits ? fi->align_bits : 8;
|
||||
}
|
||||
usize field_align_bits = fsize_align;
|
||||
|
||||
if (is_union) {
|
||||
vec_push(out->field_bit_offsets, 0);
|
||||
vec_push(out->field_bit_sizes, fsize_bits);
|
||||
if (fsize_bits > out->size_bits)
|
||||
out->size_bits = fsize_bits;
|
||||
} else {
|
||||
offset_bits = round_up_bits(offset_bits, field_align_bits);
|
||||
vec_push(out->field_bit_offsets, offset_bits);
|
||||
vec_push(out->field_bit_sizes, fsize_bits);
|
||||
offset_bits += fsize_bits;
|
||||
}
|
||||
if (field_align_bits > max_align_bits)
|
||||
max_align_bits = field_align_bits;
|
||||
}
|
||||
|
||||
usize final_align_bits = max_align_bits;
|
||||
if (layout->aggregate.align_rule == ALIGN_FIXED) {
|
||||
final_align_bits = layout->aggregate.fixed_align * 8;
|
||||
} else if (layout->aggregate.align_rule == ALIGN_MAX_WITH_MIN) {
|
||||
usize min_bits = layout->aggregate.fixed_align * 8;
|
||||
if (final_align_bits < min_bits)
|
||||
final_align_bits = min_bits;
|
||||
}
|
||||
|
||||
if (is_union)
|
||||
out->size_bits = round_up_bits(out->size_bits, final_align_bits);
|
||||
else
|
||||
out->size_bits = round_up_bits(offset_bits, final_align_bits);
|
||||
out->align_bits = final_align_bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void resolve_type(spl_layout_t *layout, spl_type_t *type, spl_type_id_t id) {
|
||||
spl_layout_info_t *info = infos_at(layout, id);
|
||||
if (!info || info->resolved)
|
||||
return;
|
||||
|
||||
spl_type_node_t *tn = spl_type_node(type, id);
|
||||
if (!tn)
|
||||
return;
|
||||
|
||||
/* 先标记已解析:自引用类型(通过 ptr)不会无限递归,
|
||||
但保持防御性写法。 */
|
||||
info->resolved = true;
|
||||
|
||||
switch (tn->kind) {
|
||||
case SPL_TYPE_ERROR:
|
||||
case SPL_TYPE_INFER:
|
||||
case SPL_TYPE_FORALL:
|
||||
case SPL_TYPE_EXISTS:
|
||||
case SPL_TYPE_NEVER:
|
||||
case SPL_TYPE_UNIT:
|
||||
info->size_bits = 0;
|
||||
info->align_bits = 8;
|
||||
return;
|
||||
|
||||
case SPL_TYPE_ANY:
|
||||
/* 顶层类型:stage1 按不透明指针处理 */
|
||||
layout_ptr(layout, info);
|
||||
return;
|
||||
|
||||
case SPL_TYPE_BOOL:
|
||||
case SPL_TYPE_INT:
|
||||
case SPL_TYPE_FLOAT:
|
||||
layout_scalar(layout, tn, info);
|
||||
return;
|
||||
|
||||
case SPL_TYPE_PTR:
|
||||
layout_ptr(layout, info);
|
||||
return;
|
||||
|
||||
case SPL_TYPE_ARRAY: {
|
||||
resolve_type(layout, type, tn->array_type.element);
|
||||
spl_layout_info_t *elem = infos_at(layout, tn->array_type.element);
|
||||
if (!elem || !elem->resolved)
|
||||
return;
|
||||
info->size_bits = elem->size_bits * tn->array_type.len;
|
||||
info->align_bits = elem->align_bits;
|
||||
return;
|
||||
}
|
||||
|
||||
case SPL_TYPE_STRUCT:
|
||||
case SPL_TYPE_UNION:
|
||||
case SPL_TYPE_ENUM:
|
||||
case SPL_TYPE_SHAPE: {
|
||||
if (tn->kind == SPL_TYPE_ENUM && tn->agg_type.tag_type)
|
||||
resolve_type(layout, type, tn->agg_type.tag_type);
|
||||
for (usize i = 0; i < vec_size(tn->agg_type.variants); ++i) {
|
||||
spl_type_member_t *m = &vec_at(tn->agg_type.variants, i);
|
||||
if (m->type_id)
|
||||
resolve_type(layout, type, m->type_id);
|
||||
}
|
||||
layout_agg(layout, tn, info);
|
||||
return;
|
||||
}
|
||||
|
||||
case SPL_TYPE_FN:
|
||||
/* 函数类型按函数指针布局 */
|
||||
for (usize i = 0; i < vec_size(tn->fn_type.params); ++i)
|
||||
resolve_type(layout, type, vec_at(tn->fn_type.params, i));
|
||||
resolve_type(layout, type, tn->fn_type.ret);
|
||||
layout_ptr(layout, info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void spl_layout_resolve(spl_layout_t *layout, spl_type_t *type) {
|
||||
infos_grow(layout, vec_size(type->type_table));
|
||||
for (usize i = 1; i < vec_size(type->type_table); ++i)
|
||||
resolve_type(layout, type, i);
|
||||
}
|
||||
|
||||
const spl_layout_info_t *spl_layout_info_of(spl_layout_t *layout, spl_type_id_t id) {
|
||||
spl_layout_info_t *info = infos_at(layout, id);
|
||||
if (!info || !info->resolved)
|
||||
return NULL;
|
||||
return info;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef __SPL_LAYOUT_H__
|
||||
#define __SPL_LAYOUT_H__
|
||||
|
||||
#include "spl_type.h"
|
||||
|
||||
typedef struct {
|
||||
usize size;
|
||||
usize abi_align;
|
||||
usize pref_align;
|
||||
VEC(usize) invalid_values;
|
||||
} spl_layout_scalar_t;
|
||||
|
||||
typedef struct {
|
||||
usize add_space;
|
||||
usize ptr_size;
|
||||
usize ptr_align;
|
||||
} spl_layout_ptr_t;
|
||||
|
||||
typedef struct {
|
||||
bool allow_field_reordering;
|
||||
enum { ALIGN_MAX, ALIGN_FIXED, ALIGN_MAX_WITH_MIN } align_rule;
|
||||
usize fixed_align;
|
||||
} spl_layout_agg_t;
|
||||
|
||||
typedef struct {
|
||||
usize size_bits;
|
||||
usize align_bits;
|
||||
VEC(usize) field_bit_offsets;
|
||||
VEC(usize) field_bit_sizes;
|
||||
bool resolved;
|
||||
} spl_layout_info_t;
|
||||
|
||||
typedef struct {
|
||||
enum { SPL_BIG_ENDIAN, SPL_LITTLE_ENDIAN } endian; // TODO 更多位存储方式
|
||||
usize ptr_size;
|
||||
usize ptr_align;
|
||||
} spl_layout_target_t;
|
||||
|
||||
typedef struct {
|
||||
VEC(spl_layout_scalar_t) scalars;
|
||||
VEC(spl_layout_ptr_t) pointers;
|
||||
spl_layout_agg_t aggregate;
|
||||
VEC(spl_layout_info_t) infos;
|
||||
spl_layout_target_t *target;
|
||||
} spl_layout_t;
|
||||
|
||||
void spl_layout_init(spl_layout_t *layout, spl_layout_target_t *target);
|
||||
void spl_layout_drop(spl_layout_t *layout);
|
||||
|
||||
void spl_layout_resolve(spl_layout_t *layout, spl_type_t *type);
|
||||
|
||||
void spl_layout_add_scalar(spl_layout_t *layout, usize size, usize abi_align);
|
||||
void spl_layout_add_ptr(spl_layout_t *layout, usize addr_space, usize size, usize align);
|
||||
|
||||
const spl_layout_info_t *spl_layout_info_of(spl_layout_t *layout, spl_type_id_t id);
|
||||
|
||||
#endif /* __SPL_LAYOUT_H__ */
|
||||
@@ -0,0 +1,499 @@
|
||||
/* spl_lexer.c - SPL lexical analyzer */
|
||||
|
||||
#include "spl_lexer.h"
|
||||
#include "spl_tok.h"
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Character classification */
|
||||
static int is_ident_start(char c) { return isalpha((unsigned char)c) || c == '_'; }
|
||||
|
||||
static int is_ident_cont(char c) { return isalnum((unsigned char)c) || c == '_'; }
|
||||
|
||||
/* Keyword lookup: if ident is a keyword, return its token type, else TOK_IDENT */
|
||||
static spl_tok_type_t keyword_type(const char *ident, usize len) {
|
||||
#define X(name, enum_name, dummy) \
|
||||
if (len == sizeof(#name) - 1 && memcmp(ident, #name, len) == 0) \
|
||||
return enum_name;
|
||||
KEYWORD_TABLE
|
||||
#undef X
|
||||
return TOK_IDENT;
|
||||
}
|
||||
|
||||
/* Escape sequence decoder - returns the decoded character,
|
||||
* advances *s past the escape sequence, returns 0 on success,
|
||||
* non-zero on error. */
|
||||
int spl_decode_escape(const char **s, char *out) {
|
||||
if (**s != '\\') {
|
||||
*out = **s;
|
||||
(*s)++;
|
||||
return 0;
|
||||
}
|
||||
(*s)++; /* skip backslash */
|
||||
switch (**s) {
|
||||
case 'n':
|
||||
*out = '\n';
|
||||
break;
|
||||
case 't':
|
||||
*out = '\t';
|
||||
break;
|
||||
case 'r':
|
||||
*out = '\r';
|
||||
break;
|
||||
case '\\':
|
||||
*out = '\\';
|
||||
break;
|
||||
case '"':
|
||||
*out = '"';
|
||||
break;
|
||||
case '\'':
|
||||
*out = '\'';
|
||||
break;
|
||||
case '0':
|
||||
*out = '\0';
|
||||
break;
|
||||
case 'x': {
|
||||
(*s)++;
|
||||
char hex[3] = {0, 0, 0};
|
||||
int i;
|
||||
for (i = 0; i < 2 && isxdigit((unsigned char)**s); i++, (*s)++) {
|
||||
hex[i] = **s;
|
||||
}
|
||||
if (i == 0)
|
||||
return -1;
|
||||
*out = (char)strtol(hex, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
(*s)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
spl_tok_vec_t spl_lex(const char *source, const char *fname) {
|
||||
spl_tok_vec_t toks;
|
||||
vec_init(toks);
|
||||
|
||||
usize line = 1;
|
||||
usize col = 1;
|
||||
usize offset = 0;
|
||||
usize len = strlen(source);
|
||||
|
||||
while (offset < len) {
|
||||
const char *start = source + offset;
|
||||
char c = *start;
|
||||
|
||||
/* Skip whitespace (but not newlines - emit TOK_ENDLINE) */
|
||||
if (c == ' ' || c == '\t' || c == '\r') {
|
||||
offset++;
|
||||
col++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Newline */
|
||||
if (c == '\n') {
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_ENDLINE;
|
||||
tok.lexeme = start;
|
||||
tok.len = 1;
|
||||
tok.fname = fname;
|
||||
tok.offset = offset;
|
||||
tok.line = line;
|
||||
tok.col = col;
|
||||
vec_push(toks, tok);
|
||||
offset++;
|
||||
line++;
|
||||
col = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Line comment */
|
||||
if (c == '/' && offset + 1 < len && source[offset + 1] == '/') {
|
||||
const char *nl = (const char *)memchr(start, '\n', len - offset);
|
||||
usize clen = nl ? (usize)(nl - start) : (len - offset);
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_LINE_COMMENT;
|
||||
tok.lexeme = start;
|
||||
tok.len = clen;
|
||||
tok.fname = fname;
|
||||
tok.offset = offset;
|
||||
tok.line = line;
|
||||
tok.col = col;
|
||||
vec_push(toks, tok);
|
||||
offset += clen;
|
||||
col += clen;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Block comment */
|
||||
if (c == '/' && offset + 1 < len && source[offset + 1] == '*') {
|
||||
offset += 2;
|
||||
col += 2;
|
||||
usize depth = 1;
|
||||
while (offset + 1 < len && depth > 0) {
|
||||
if (source[offset] == '*' && source[offset + 1] == '/') {
|
||||
depth--;
|
||||
offset += 2;
|
||||
col += 2;
|
||||
if (depth == 0)
|
||||
break;
|
||||
} else if (source[offset] == '/' && source[offset + 1] == '*') {
|
||||
depth++;
|
||||
offset += 2;
|
||||
col += 2;
|
||||
} else {
|
||||
if (source[offset] == '\n') {
|
||||
line++;
|
||||
col = 1;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Char literal: 'x' */
|
||||
if (c == '\'') {
|
||||
offset++;
|
||||
col++;
|
||||
char buf[16];
|
||||
int bi = 0;
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
if (offset < len) {
|
||||
const char *cp = source + offset;
|
||||
if (spl_decode_escape(&cp, &buf[bi])) {
|
||||
buf[bi] = source[offset];
|
||||
cp = source + offset + 1;
|
||||
}
|
||||
bi++;
|
||||
offset = (usize)(cp - source);
|
||||
col += (usize)(cp - (start + 1));
|
||||
}
|
||||
|
||||
if (offset < len && source[offset] == '\'') {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_CHAR_LITERAL;
|
||||
tok.lexeme = start;
|
||||
tok.len = (usize)((source + offset) - start);
|
||||
tok.fname = fname;
|
||||
tok.offset = start - source;
|
||||
tok.line = line;
|
||||
tok.col = col - tok.len;
|
||||
vec_push(toks, tok);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* String literal: "..." */
|
||||
if (c == '"') {
|
||||
offset++;
|
||||
col++;
|
||||
while (offset < len) {
|
||||
if (source[offset] == '\\') {
|
||||
offset++;
|
||||
col++;
|
||||
if (offset < len) {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
} else if (source[offset] == '"') {
|
||||
offset++;
|
||||
col++;
|
||||
break;
|
||||
} else if (source[offset] == '\n') {
|
||||
line++;
|
||||
col = 1;
|
||||
offset++;
|
||||
} else {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
}
|
||||
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_STRING_LITERAL;
|
||||
tok.lexeme = start;
|
||||
tok.len = (usize)((source + offset) - start);
|
||||
tok.fname = fname;
|
||||
tok.offset = start - source;
|
||||
tok.line = line;
|
||||
tok.col = col - tok.len;
|
||||
vec_push(toks, tok);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Identifiers and keywords */
|
||||
if (is_ident_start(c)) {
|
||||
const char *id_start = start;
|
||||
usize id_len = 0;
|
||||
while (offset < len && is_ident_cont(source[offset])) {
|
||||
offset++;
|
||||
id_len++;
|
||||
col++;
|
||||
}
|
||||
|
||||
spl_tok_type_t tt = keyword_type(id_start, id_len);
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = tt;
|
||||
tok.lexeme = id_start;
|
||||
tok.len = id_len;
|
||||
tok.fname = fname;
|
||||
tok.offset = id_start - source;
|
||||
tok.line = line;
|
||||
tok.col = col - id_len;
|
||||
vec_push(toks, tok);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Numbers: integers and floats */
|
||||
if (isdigit((unsigned char)c)) {
|
||||
const char *num_start = start;
|
||||
int is_float = 0;
|
||||
|
||||
/* Check for hex/bin/oct prefix */
|
||||
if (c == '0' && offset + 1 < len) {
|
||||
char nc = source[offset + 1];
|
||||
if (nc == 'x' || nc == 'X') {
|
||||
/* Hex literal */
|
||||
offset += 2;
|
||||
col += 2;
|
||||
while (offset < len &&
|
||||
(isxdigit((unsigned char)source[offset]) || source[offset] == '_')) {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
goto emit_int;
|
||||
}
|
||||
if (nc == 'b' || nc == 'B') {
|
||||
/* Binary literal */
|
||||
offset += 2;
|
||||
col += 2;
|
||||
while (offset < len && (source[offset] == '0' || source[offset] == '1' ||
|
||||
source[offset] == '_')) {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
goto emit_int;
|
||||
}
|
||||
if (nc == 'o' || nc == 'O') {
|
||||
/* Octal literal */
|
||||
offset += 2;
|
||||
col += 2;
|
||||
while (offset < len && ((source[offset] >= '0' && source[offset] <= '7') ||
|
||||
source[offset] == '_')) {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
goto emit_int;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decimal integer or float */
|
||||
while (offset < len &&
|
||||
(isdigit((unsigned char)source[offset]) || source[offset] == '_')) {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
|
||||
if (offset < len && source[offset] == '.' && offset + 1 < len &&
|
||||
isdigit((unsigned char)source[offset + 1])) {
|
||||
is_float = 1;
|
||||
offset++;
|
||||
col++;
|
||||
while (offset < len && isdigit((unsigned char)source[offset])) {
|
||||
offset++;
|
||||
col++;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_float) {
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_FLOAT_LITERAL;
|
||||
tok.lexeme = num_start;
|
||||
tok.len = (usize)((source + offset) - num_start);
|
||||
tok.fname = fname;
|
||||
tok.offset = num_start - source;
|
||||
tok.line = line;
|
||||
tok.col = col - tok.len;
|
||||
vec_push(toks, tok);
|
||||
continue;
|
||||
}
|
||||
|
||||
emit_int:
|
||||
{
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_INT_LITERAL;
|
||||
tok.lexeme = num_start;
|
||||
tok.len = (usize)((source + offset) - num_start);
|
||||
tok.fname = fname;
|
||||
tok.offset = num_start - source;
|
||||
tok.line = line;
|
||||
tok.col = col - tok.len;
|
||||
vec_push(toks, tok);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Multi-character operators (longest match) */
|
||||
#define TRY_OP1(c1, tok1) \
|
||||
if (c == (c1)) { \
|
||||
spl_tok_t tok; \
|
||||
memset(&tok, 0, sizeof(tok)); \
|
||||
tok.type = (tok1); \
|
||||
tok.lexeme = start; \
|
||||
tok.len = 1; \
|
||||
tok.fname = fname; \
|
||||
tok.offset = offset; \
|
||||
tok.line = line; \
|
||||
tok.col = col; \
|
||||
vec_push(toks, tok); \
|
||||
offset++; \
|
||||
col++; \
|
||||
continue; \
|
||||
}
|
||||
|
||||
/* Helper: try to match a two-char operator */
|
||||
#define TRY_OP2(c1, c2, tok2, tok1) \
|
||||
if (c == (c1) && offset + 1 < len && source[offset + 1] == (c2)) { \
|
||||
spl_tok_type_t op_type = (tok2); \
|
||||
usize op_len = 2; \
|
||||
spl_tok_t tok; \
|
||||
memset(&tok, 0, sizeof(tok)); \
|
||||
tok.type = op_type; \
|
||||
tok.lexeme = start; \
|
||||
tok.len = op_len; \
|
||||
tok.fname = fname; \
|
||||
tok.offset = offset; \
|
||||
tok.line = line; \
|
||||
tok.col = col; \
|
||||
vec_push(toks, tok); \
|
||||
offset += op_len; \
|
||||
col += op_len; \
|
||||
continue; \
|
||||
}
|
||||
|
||||
#define TRY_OP3(c1, c2, c3, tok3, tok2, tok1) \
|
||||
if (c == (c1) && offset + 1 < len && source[offset + 1] == (c2)) { \
|
||||
spl_tok_type_t op_type = (tok2); \
|
||||
usize op_len = 2; \
|
||||
if (offset + 2 < len && source[offset + 2] == (c3)) { \
|
||||
op_type = (tok3); \
|
||||
op_len = 3; \
|
||||
} \
|
||||
spl_tok_t tok; \
|
||||
memset(&tok, 0, sizeof(tok)); \
|
||||
tok.type = op_type; \
|
||||
tok.lexeme = start; \
|
||||
tok.len = op_len; \
|
||||
tok.fname = fname; \
|
||||
tok.offset = offset; \
|
||||
tok.line = line; \
|
||||
tok.col = col; \
|
||||
vec_push(toks, tok); \
|
||||
offset += op_len; \
|
||||
col += op_len; \
|
||||
continue; \
|
||||
}
|
||||
|
||||
/* Three-char operators first */
|
||||
TRY_OP3('<', '-', '>', TOK_ARROW_BOTH, TOK_LEFT_ARROW, TOK_LT)
|
||||
TRY_OP3('<', '=', '>', TOK_FAT_ARROW_BOTH, TOK_LE, TOK_LT)
|
||||
TRY_OP3('<', '<', '=', TOK_ASSIGN_L_SH, TOK_L_SH, TOK_LT)
|
||||
TRY_OP3('>', '>', '=', TOK_ASSIGN_R_SH, TOK_R_SH, TOK_GT)
|
||||
TRY_OP3('.', '.', '.', TOK_ELLIPSIS, TOK_RANGE, TOK_DOT)
|
||||
|
||||
/* Two-char operators */
|
||||
TRY_OP2('=', '=', TOK_EQ, TOK_ASSIGN)
|
||||
TRY_OP2('=', '>', TOK_RIGHT_FAT_ARROW, TOK_ASSIGN)
|
||||
TRY_OP2('!', '=', TOK_NEQ, TOK_NOT)
|
||||
TRY_OP2('<', '=', TOK_LE, TOK_LT)
|
||||
TRY_OP2('>', '=', TOK_GE, TOK_GT)
|
||||
TRY_OP2('&', '&', TOK_AND_AND, TOK_AND)
|
||||
TRY_OP2('|', '|', TOK_OR_OR, TOK_OR)
|
||||
TRY_OP2('+', '=', TOK_ASSIGN_ADD, TOK_ADD)
|
||||
TRY_OP2('-', '=', TOK_ASSIGN_SUB, TOK_SUB)
|
||||
TRY_OP2('*', '=', TOK_ASSIGN_MUL, TOK_MUL)
|
||||
TRY_OP2('/', '=', TOK_ASSIGN_DIV, TOK_DIV)
|
||||
TRY_OP2('%', '=', TOK_ASSIGN_MOD, TOK_MOD)
|
||||
TRY_OP2('&', '=', TOK_ASSIGN_AND, TOK_AND)
|
||||
TRY_OP2('|', '=', TOK_ASSIGN_OR, TOK_OR)
|
||||
TRY_OP2('^', '=', TOK_ASSIGN_XOR, TOK_XOR)
|
||||
TRY_OP2('-', '>', TOK_RIGHT_ARROW, TOK_SUB)
|
||||
TRY_OP2(':', '=', TOK_COLON_ASSIGN, TOK_COLON)
|
||||
|
||||
/* Single-character operators */
|
||||
TRY_OP1('+', TOK_ADD);
|
||||
TRY_OP1('-', TOK_SUB);
|
||||
TRY_OP1('*', TOK_MUL);
|
||||
TRY_OP1('/', TOK_DIV);
|
||||
TRY_OP1('%', TOK_MOD);
|
||||
TRY_OP1('&', TOK_AND);
|
||||
TRY_OP1('|', TOK_OR);
|
||||
TRY_OP1('^', TOK_XOR);
|
||||
TRY_OP1('~', TOK_BIT_NOT);
|
||||
TRY_OP1('!', TOK_NOT);
|
||||
TRY_OP1('<', TOK_LT);
|
||||
TRY_OP1('>', TOK_GT);
|
||||
TRY_OP1('=', TOK_ASSIGN);
|
||||
TRY_OP1('.', TOK_DOT);
|
||||
TRY_OP1(',', TOK_COMMA);
|
||||
TRY_OP1(';', TOK_SEMICOLON);
|
||||
TRY_OP1(':', TOK_COLON);
|
||||
TRY_OP1('(', TOK_L_PAREN);
|
||||
TRY_OP1(')', TOK_R_PAREN);
|
||||
TRY_OP1('[', TOK_L_BRACKET);
|
||||
TRY_OP1(']', TOK_R_BRACKET);
|
||||
TRY_OP1('{', TOK_L_BRACE);
|
||||
TRY_OP1('}', TOK_R_BRACE);
|
||||
TRY_OP1('#', TOK_SHARP);
|
||||
TRY_OP1('@', TOK_AT);
|
||||
TRY_OP1('$', TOK_DOLLAR);
|
||||
TRY_OP1('?', TOK_COND);
|
||||
|
||||
// TODO
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_UNKNOWN;
|
||||
tok.lexeme = start;
|
||||
tok.len = 1;
|
||||
tok.fname = fname;
|
||||
tok.offset = offset;
|
||||
tok.line = line;
|
||||
tok.col = col;
|
||||
vec_push(toks, tok);
|
||||
offset++;
|
||||
col++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* EOF token */
|
||||
{
|
||||
spl_tok_t tok;
|
||||
memset(&tok, 0, sizeof(tok));
|
||||
tok.type = TOK_EOF;
|
||||
tok.lexeme = source + offset;
|
||||
tok.len = 0;
|
||||
tok.fname = fname;
|
||||
tok.offset = offset;
|
||||
tok.line = line;
|
||||
tok.col = col;
|
||||
vec_push(toks, tok);
|
||||
}
|
||||
|
||||
return toks;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* spl_lexer.h - 独立词法分析器 */
|
||||
#ifndef __SPL_LEXER_H__
|
||||
#define __SPL_LEXER_H__
|
||||
|
||||
#include "spl_tok.h"
|
||||
|
||||
/* Lexer entry point */
|
||||
spl_tok_vec_t spl_lex(const char *source, const char *fname);
|
||||
|
||||
/* Decode escape sequence, advance *s past it. Returns 0 on success. */
|
||||
int spl_decode_escape(const char **s, char *out);
|
||||
|
||||
#endif /* __SPL_LEXER_H__ */
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef __SPL_MPA_H__
|
||||
#define __SPL_MPA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint64_t bits;
|
||||
} spl_mpa_int_t;
|
||||
|
||||
typedef struct {
|
||||
double bits;
|
||||
} spl_mpa_float_t;
|
||||
|
||||
#endif /* __SPL_MPA_H__ */
|
||||
+2291
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
||||
#ifndef __SPL_PARSER_H__
|
||||
#define __SPL_PARSER_H__
|
||||
|
||||
#include "spl_ast.h"
|
||||
#include "spl_diag.h"
|
||||
|
||||
typedef struct spl_parser {
|
||||
spl_ast_t *ast;
|
||||
spl_diag_t *diag;
|
||||
usize pos;
|
||||
int failed;
|
||||
int stop_agg;
|
||||
usize error_count;
|
||||
} spl_parser_t;
|
||||
|
||||
void spl_parser_init(spl_parser_t *p);
|
||||
void spl_parser_drop(spl_parser_t *p);
|
||||
|
||||
void spl_ast_parse(spl_parser_t *p, spl_ast_t *ast);
|
||||
|
||||
#endif /* __SPL_PARSER_H__ */
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "spl_scope.h"
|
||||
|
||||
void spl_scope_init(spl_scope_t *scope) {
|
||||
vec_init(scope->scopes);
|
||||
vec_push(scope->scopes, (spl_scope_node_t){0});
|
||||
scope->current_scope = 0;
|
||||
scope->root_scope = 0;
|
||||
}
|
||||
|
||||
void spl_scope_drop(spl_scope_t *scope) {
|
||||
for (usize i = 0; i < vec_size(scope->scopes); ++i) {
|
||||
map_free(vec_at(scope->scopes, i).symbols);
|
||||
}
|
||||
vec_free(scope->scopes);
|
||||
scope->current_scope = 0;
|
||||
scope->root_scope = 0;
|
||||
}
|
||||
|
||||
spl_scope_id_t spl_scope_alloc(spl_scope_t *scope) {
|
||||
spl_scope_node_t n = {0};
|
||||
n.parent = scope->current_scope;
|
||||
map_init(n.symbols, MAP_HASH_STR, MAP_CMP_STR);
|
||||
vec_push(scope->scopes, n);
|
||||
return vec_size(scope->scopes) - 1;
|
||||
}
|
||||
|
||||
bool spl_scope_insert(spl_scope_t *scope, spl_scope_id_t id, spl_symbol_t symbol) {
|
||||
Assert(id != 0 && id < vec_size(scope->scopes));
|
||||
spl_scope_node_t *node = &vec_at(scope->scopes, id);
|
||||
spl_symbol_t old = {0};
|
||||
if (map_get(node->symbols, symbol.name, &old))
|
||||
return false;
|
||||
map_put(node->symbols, symbol.name, symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool spl_scope_find(spl_scope_t *scope, const char *name, spl_symbol_t *out) {
|
||||
if (!scope || !name || !out)
|
||||
return false;
|
||||
for (spl_scope_id_t id = scope->current_scope; id != 0; id = vec_at(scope->scopes, id).parent) {
|
||||
spl_scope_node_t *s = &vec_at(scope->scopes, id);
|
||||
if (map_get(s->symbols, name, out))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef __SPL_SCOPE_H__
|
||||
#define __SPL_SCOPE_H__
|
||||
|
||||
#include "spl_lang_type.h"
|
||||
|
||||
typedef usize spl_scope_id_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_SYMBOL_KIND_ERROR,
|
||||
SPL_SYMBOL_KIND_VAR,
|
||||
SPL_SYMBOL_KIND_MEMBER,
|
||||
SPL_SYMBOL_KIND_FN,
|
||||
SPL_SYMBOL_KIND_TYPE,
|
||||
} spl_symbol_kind_t;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
spl_symbol_kind_t kind;
|
||||
spl_def_id_t node;
|
||||
} spl_symbol_t;
|
||||
|
||||
typedef VEC(const char *) spl_symbol_path_t;
|
||||
|
||||
typedef struct {
|
||||
spl_scope_id_t parent;
|
||||
MAP(const char *, spl_symbol_t) symbols;
|
||||
} spl_scope_node_t;
|
||||
typedef VEC(spl_scope_node_t) spl_scope_node_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_scope_node_vec_t scopes;
|
||||
spl_scope_id_t root_scope;
|
||||
spl_scope_id_t current_scope;
|
||||
} spl_scope_t;
|
||||
|
||||
void spl_scope_init(spl_scope_t *scope);
|
||||
void spl_scope_drop(spl_scope_t *scope);
|
||||
spl_scope_id_t spl_scope_alloc(spl_scope_t *scope);
|
||||
bool spl_scope_insert(spl_scope_t *scope, spl_scope_id_t id, spl_symbol_t symbol);
|
||||
bool spl_scope_find(spl_scope_t *scop, const char *symbol_name, spl_symbol_t *out);
|
||||
|
||||
#endif /* __SPL_SCOPE_H__ */
|
||||
+2907
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,45 @@
|
||||
#ifndef __SPL_SEMA_H__
|
||||
#define __SPL_SEMA_H__
|
||||
|
||||
#include "spl_ast.h"
|
||||
#include "spl_attr.h"
|
||||
#include "spl_diag.h"
|
||||
#include "spl_lang_type.h"
|
||||
#include "spl_layout.h"
|
||||
#include "spl_mpa.h"
|
||||
#include "spl_scope.h"
|
||||
#include "spl_type.h"
|
||||
|
||||
typedef struct spl_module_loader spl_module_loader_t;
|
||||
struct spl_module_loader {
|
||||
/* 从 from_fname 所在目录解析 path;返回该文件的 ast(已 init + parse)。
|
||||
失败(文件不存在 / parse 错误 / 环)返回 NULL。
|
||||
loader 拥有 ast 的生命周期;sema 不负责释放。 */
|
||||
spl_ast_t *(*load)(void *user, const char *from_fname, const char *path, spl_diag_t *diag);
|
||||
void *user;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
spl_ast_t *ast;
|
||||
spl_lang_type_t *type;
|
||||
spl_layout_t *layout; /* 由 splc0 拥有并解析;sema 仅借用 */
|
||||
spl_scope_t *scope;
|
||||
spl_symbol_t root;
|
||||
spl_attr_t *attr;
|
||||
spl_lang_type_id_t current_aggregate_lang;
|
||||
spl_lang_type_id_t current_fn_ret_tid;
|
||||
spl_lang_type_id_t current_loop_break_tid;
|
||||
spl_symbol_path_t label_stack;
|
||||
const spl_module_loader_t *loader;
|
||||
spl_diag_t *diag;
|
||||
} spl_sema_t;
|
||||
|
||||
void spl_sema_init(spl_sema_t *sema, spl_ast_t *ast, spl_lang_type_t *type,
|
||||
spl_layout_t *layout, spl_scope_t *scope, spl_attr_t *attr);
|
||||
void spl_sema_drop(spl_sema_t *sema);
|
||||
|
||||
void spl_sema_run_module(spl_sema_t *sema, spl_ast_node_ref_t module_ref);
|
||||
void spl_sema_run(spl_sema_t *sema);
|
||||
void spl_sema_dump(spl_sema_t *sema);
|
||||
|
||||
#endif /* __SPL_SEMA_H__ */
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "spl_tok.h"
|
||||
|
||||
const char *spl_tok_type_name(spl_tok_type_t t) {
|
||||
static const char *const names[] = {
|
||||
#define X(name, enum_name, dummy) #enum_name,
|
||||
KEYWORD_TABLE
|
||||
#undef X
|
||||
#define X(name, enum_name, dummy) #enum_name,
|
||||
TOKEN_TABLE
|
||||
#undef X
|
||||
};
|
||||
if ((usize)t < sizeof(names) / sizeof(names[0]))
|
||||
return names[t];
|
||||
return "?";
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/* spl_tok.h - Token type definitions (extracted from spl_lexer.h) */
|
||||
#ifndef __SPL_TOK_H__
|
||||
#define __SPL_TOK_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
|
||||
/* clang-format off */
|
||||
#define KEYWORD_TABLE \
|
||||
X(any , KW_ANY , SPL_V0) \
|
||||
X(as , KW_AS , SPL_V0) \
|
||||
X(bool , KW_BOOL , SPL_V0) \
|
||||
X(break , KW_BREAK , SPL_V0) \
|
||||
X(catch , KW_CATCH , SPL_V0) \
|
||||
X(comptime , KW_COMPTIME , SPL_V0) \
|
||||
X(const , KW_CONST , SPL_V0) \
|
||||
X(continue , KW_CONTINUE , SPL_V0) \
|
||||
X(defer , KW_DEFER , SPL_V0) \
|
||||
X(else , KW_ELSE , SPL_V0) \
|
||||
X(enum , KW_ENUM , SPL_V0) \
|
||||
X(errdefer , KW_ERRDEFER , SPL_V0) \
|
||||
X(false , KW_FALSE , SPL_V0) \
|
||||
X(fn , KW_FN , SPL_V0) \
|
||||
X(for , KW_FOR , SPL_V0) \
|
||||
X(if , KW_IF , SPL_V0) \
|
||||
X(loop , KW_LOOP , SPL_V0) \
|
||||
X(match , KW_MATCH , SPL_V0) \
|
||||
X(null , KW_NULL , SPL_V0) \
|
||||
X(ret , KW_RET , SPL_V0) \
|
||||
X(shape , KW_SHAPE , SPL_V0) \
|
||||
X(struct , KW_STRUCT , SPL_V0) \
|
||||
X(true , KW_TRUE , SPL_V0) \
|
||||
X(try , KW_TRY , SPL_V0) \
|
||||
X(type , KW_TYPE , SPL_V0) \
|
||||
X(undefined , KW_UNDEFINED , SPL_V0) \
|
||||
X(union , KW_UNION , SPL_V0) \
|
||||
X(var , KW_VAR , SPL_V0) \
|
||||
X(while , KW_WHILE , SPL_V0) \
|
||||
X(_ , KW_UNDERSCORE , SPL_V0) \
|
||||
// KEYWORD_TABLE
|
||||
|
||||
#define TOKEN_TABLE \
|
||||
X(unknown , TOK_UNKNOWN , SPL_V0 ) \
|
||||
X(EOF , TOK_EOF , SPL_V0 ) \
|
||||
X(blank , TOK_BLANK , SPL_V0 ) \
|
||||
X(endline , TOK_ENDLINE , SPL_V0 ) \
|
||||
X("#" , TOK_SHARP , SPL_V0 ) \
|
||||
X("@" , TOK_AT , SPL_V0 ) \
|
||||
X("$" , TOK_DOLLAR , SPL_V0 ) \
|
||||
X("==" , TOK_EQ , SPL_V0 ) \
|
||||
X("=" , TOK_ASSIGN , SPL_V0 ) \
|
||||
X("+=" , TOK_ASSIGN_ADD , SPL_V0 ) \
|
||||
X("+" , TOK_ADD , SPL_V0 ) \
|
||||
X("-=" , TOK_ASSIGN_SUB , SPL_V0 ) \
|
||||
X("->" , TOK_RIGHT_ARROW , SPL_V0 ) \
|
||||
X("<-" , TOK_LEFT_ARROW , SPL_V0 ) \
|
||||
X("<->" , TOK_ARROW_BOTH , SPL_V0 ) \
|
||||
X("<=>" , TOK_FAT_ARROW_BOTH , SPL_V0 ) \
|
||||
X("=>" , TOK_RIGHT_FAT_ARROW , SPL_V0 ) \
|
||||
X("-" , TOK_SUB , SPL_V0 ) \
|
||||
X("*=" , TOK_ASSIGN_MUL , SPL_V0 ) \
|
||||
X("*" , TOK_MUL , SPL_V0 ) \
|
||||
X("/=" , TOK_ASSIGN_DIV , SPL_V0 ) \
|
||||
X("/" , TOK_DIV , SPL_V0 ) \
|
||||
X("//" , TOK_LINE_COMMENT , SPL_V0 ) \
|
||||
X("/* */" , TOK_BLOCK_COMMENT , SPL_V0 ) \
|
||||
X("%=" , TOK_ASSIGN_MOD , SPL_V0 ) \
|
||||
X("%" , TOK_MOD , SPL_V0 ) \
|
||||
X("&&" , TOK_AND_AND , SPL_V0 ) \
|
||||
X("&=" , TOK_ASSIGN_AND , SPL_V0 ) \
|
||||
X("&" , TOK_AND , SPL_V0 ) \
|
||||
X("||" , TOK_OR_OR , SPL_V0 ) \
|
||||
X("|=" , TOK_ASSIGN_OR , SPL_V0 ) \
|
||||
X("|" , TOK_OR , SPL_V0 ) \
|
||||
X("^=" , TOK_ASSIGN_XOR , SPL_V0 ) \
|
||||
X("^" , TOK_XOR , SPL_V0 ) \
|
||||
X("<<=" , TOK_ASSIGN_L_SH , SPL_V0 ) \
|
||||
X("<<" , TOK_L_SH , SPL_V0 ) \
|
||||
X("<=" , TOK_LE , SPL_V0 ) \
|
||||
X("<" , TOK_LT , SPL_V0 ) \
|
||||
X(">>=" , TOK_ASSIGN_R_SH , SPL_V0 ) \
|
||||
X(">>" , TOK_R_SH , SPL_V0 ) \
|
||||
X(">=" , TOK_GE , SPL_V0 ) \
|
||||
X(">" , TOK_GT , SPL_V0 ) \
|
||||
X("!" , TOK_NOT , SPL_V0 ) \
|
||||
X("!=" , TOK_NEQ , SPL_V0 ) \
|
||||
X("~" , TOK_BIT_NOT , SPL_V0 ) \
|
||||
X("[" , TOK_L_BRACKET , SPL_V0 ) \
|
||||
X("]" , TOK_R_BRACKET , SPL_V0 ) \
|
||||
X("(" , TOK_L_PAREN , SPL_V0 ) \
|
||||
X(")" , TOK_R_PAREN , SPL_V0 ) \
|
||||
X("{" , TOK_L_BRACE , SPL_V0 ) \
|
||||
X("}" , TOK_R_BRACE , SPL_V0 ) \
|
||||
X(";" , TOK_SEMICOLON , SPL_V0 ) \
|
||||
X("," , TOK_COMMA , SPL_V0 ) \
|
||||
X(":" , TOK_COLON , SPL_V0 ) \
|
||||
X(":=" , TOK_COLON_ASSIGN , SPL_V0 ) \
|
||||
X("." , TOK_DOT , SPL_V0 ) \
|
||||
X(".." , TOK_RANGE , SPL_V0 ) \
|
||||
X("..." , TOK_ELLIPSIS , SPL_V0 ) \
|
||||
X("?" , TOK_COND , SPL_V0 ) \
|
||||
X(ident , TOK_IDENT , SPL_V0 ) \
|
||||
X(int , TOK_INT_LITERAL , SPL_V0 ) \
|
||||
X(float , TOK_FLOAT_LITERAL , SPL_V0 ) \
|
||||
X(char , TOK_CHAR_LITERAL , SPL_V0 ) \
|
||||
X(string , TOK_STRING_LITERAL , SPL_V0 ) \
|
||||
// TOKEN_TABLE
|
||||
/* clang-format on */
|
||||
|
||||
typedef enum {
|
||||
#define X(name, enum_name, dummy) enum_name,
|
||||
KEYWORD_TABLE
|
||||
#undef X
|
||||
#define X(name, enum_name, dummy) enum_name,
|
||||
TOKEN_TABLE
|
||||
#undef X
|
||||
} spl_tok_type_t;
|
||||
|
||||
typedef struct {
|
||||
spl_tok_type_t type;
|
||||
const char *lexeme;
|
||||
usize len;
|
||||
const char *fname;
|
||||
usize offset;
|
||||
usize line;
|
||||
usize col;
|
||||
} spl_tok_t;
|
||||
|
||||
typedef VEC(spl_tok_t) spl_tok_vec_t;
|
||||
|
||||
const char *spl_tok_type_name(spl_tok_type_t t);
|
||||
|
||||
#endif /* __SPL_TOK_H__ */
|
||||
@@ -0,0 +1,431 @@
|
||||
#include "spl_type.h"
|
||||
|
||||
static usize spl_type_hash(spl_type_node_t n) {
|
||||
usize hash = spl_hash_seed();
|
||||
hash = spl_hash_mix(hash, (usize)n.kind);
|
||||
switch (n.kind) {
|
||||
case SPL_TYPE_ERROR:
|
||||
case SPL_TYPE_UNIT:
|
||||
case SPL_TYPE_ANY:
|
||||
case SPL_TYPE_NEVER:
|
||||
case SPL_TYPE_INFER:
|
||||
case SPL_TYPE_FORALL:
|
||||
case SPL_TYPE_EXISTS:
|
||||
case SPL_TYPE_BOOL:
|
||||
break;
|
||||
case SPL_TYPE_INT:
|
||||
hash = spl_hash_mix(hash, n.int_type.bits);
|
||||
hash = spl_hash_mix(hash, (usize)n.int_type.is_signed);
|
||||
break;
|
||||
case SPL_TYPE_FLOAT:
|
||||
hash = spl_hash_mix(hash, n.float_type.bits);
|
||||
break;
|
||||
case SPL_TYPE_PTR:
|
||||
hash = spl_hash_mix(hash, n.ptr_type.pointee);
|
||||
break;
|
||||
case SPL_TYPE_ARRAY:
|
||||
hash = spl_hash_mix(hash, n.array_type.element);
|
||||
hash = spl_hash_mix(hash, n.array_type.len);
|
||||
break;
|
||||
case SPL_TYPE_STRUCT:
|
||||
case SPL_TYPE_UNION:
|
||||
case SPL_TYPE_ENUM:
|
||||
case SPL_TYPE_SHAPE:
|
||||
hash = spl_hash_mix(hash, (usize)n.layout.mode);
|
||||
hash = spl_hash_mix(hash, n.layout.fixed_align_bits);
|
||||
hash = spl_hash_mix(hash, n.agg_type.tag_type);
|
||||
hash = spl_hash_mix(hash, vec_size(n.agg_type.variants));
|
||||
for (usize i = 0; i < vec_size(n.agg_type.variants); i++) {
|
||||
spl_type_member_t *m = &vec_at(n.agg_type.variants, i);
|
||||
hash = spl_hash_mix(hash, m->type_id);
|
||||
hash = spl_hash_mix_str(hash, m->name);
|
||||
}
|
||||
break;
|
||||
case SPL_TYPE_FN:
|
||||
hash = spl_hash_mix(hash, n.fn_type.ret);
|
||||
hash = spl_hash_mix(hash, vec_size(n.fn_type.params));
|
||||
for (usize i = 0; i < vec_size(n.fn_type.params); i++) {
|
||||
hash = spl_hash_mix(hash, vec_at(n.fn_type.params, i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int spl_type_eq(spl_type_node_t n1, spl_type_node_t n2) {
|
||||
if (n1.kind != n2.kind)
|
||||
return 1;
|
||||
switch (n1.kind) {
|
||||
case SPL_TYPE_ERROR:
|
||||
case SPL_TYPE_UNIT:
|
||||
case SPL_TYPE_ANY:
|
||||
case SPL_TYPE_NEVER:
|
||||
case SPL_TYPE_INFER:
|
||||
case SPL_TYPE_FORALL:
|
||||
case SPL_TYPE_EXISTS:
|
||||
case SPL_TYPE_BOOL:
|
||||
return 0;
|
||||
case SPL_TYPE_INT:
|
||||
return (n1.int_type.bits != n2.int_type.bits) ||
|
||||
(n1.int_type.is_signed != n2.int_type.is_signed);
|
||||
case SPL_TYPE_FLOAT:
|
||||
return n1.float_type.bits != n2.float_type.bits;
|
||||
case SPL_TYPE_PTR:
|
||||
return n1.ptr_type.pointee != n2.ptr_type.pointee;
|
||||
case SPL_TYPE_ARRAY:
|
||||
return (n1.array_type.element != n2.array_type.element) ||
|
||||
(n1.array_type.len != n2.array_type.len);
|
||||
case SPL_TYPE_STRUCT:
|
||||
case SPL_TYPE_UNION:
|
||||
case SPL_TYPE_ENUM:
|
||||
case SPL_TYPE_SHAPE:
|
||||
if (n1.layout.mode != n2.layout.mode ||
|
||||
n1.layout.fixed_align_bits != n2.layout.fixed_align_bits)
|
||||
return 1;
|
||||
if (n1.agg_type.tag_type != n2.agg_type.tag_type)
|
||||
return 1;
|
||||
if (vec_size(n1.agg_type.variants) != vec_size(n2.agg_type.variants))
|
||||
return 1;
|
||||
for (usize i = 0; i < vec_size(n1.agg_type.variants); i++) {
|
||||
spl_type_member_t *a = &n1.agg_type.variants.data[i];
|
||||
spl_type_member_t *b = &n2.agg_type.variants.data[i];
|
||||
if (a->type_id != b->type_id)
|
||||
return 1;
|
||||
if ((a->name == NULL) != (b->name == NULL))
|
||||
return 1;
|
||||
if (a->name && strcmp(a->name, b->name) != 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
case SPL_TYPE_FN:
|
||||
if (n1.fn_type.ret != n2.fn_type.ret)
|
||||
return 1;
|
||||
if (vec_size(n1.fn_type.params) != vec_size(n2.fn_type.params))
|
||||
return 1;
|
||||
for (usize i = 0; i < vec_size(n1.fn_type.params); i++) {
|
||||
if (vec_at(n1.fn_type.params, i) != vec_at(n2.fn_type.params, i))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void spl_type_node_free_vecs(spl_type_node_t *tn) {
|
||||
switch (tn->kind) {
|
||||
case SPL_TYPE_STRUCT:
|
||||
case SPL_TYPE_UNION:
|
||||
case SPL_TYPE_ENUM:
|
||||
case SPL_TYPE_SHAPE:
|
||||
vec_free(tn->agg_type.variants);
|
||||
break;
|
||||
case SPL_TYPE_FN:
|
||||
vec_free(tn->fn_type.params);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* spl_fold 回调:只在链内做结构比较(同桶已保证 hash 相同)。 */
|
||||
static int spl_type_fold_eq(void *ctx, spl_fold_id_t node, const void *probe) {
|
||||
spl_type_t *type = (spl_type_t *)ctx;
|
||||
return spl_type_eq(vec_at(type->type_table, node), *(const spl_type_node_t *)probe) == 0;
|
||||
}
|
||||
|
||||
static spl_fold_id_t spl_type_fold_next(void *ctx, spl_fold_id_t node) {
|
||||
spl_type_t *type = (spl_type_t *)ctx;
|
||||
return vec_at(type->type_table, node).hash_next;
|
||||
}
|
||||
|
||||
static void spl_type_fold_set_next(void *ctx, spl_fold_id_t node, spl_fold_id_t next) {
|
||||
spl_type_t *type = (spl_type_t *)ctx;
|
||||
vec_at(type->type_table, node).hash_next = next;
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_node_push(spl_type_t *type, spl_type_node_t *tn) {
|
||||
usize hash = spl_type_hash(*tn);
|
||||
|
||||
spl_type_id_t existing = spl_fold_find(&type->type_fold, hash, tn);
|
||||
if (existing) {
|
||||
spl_type_node_free_vecs(tn);
|
||||
return existing;
|
||||
}
|
||||
|
||||
spl_type_id_t id = vec_size(type->type_table);
|
||||
vec_push(type->type_table, *tn);
|
||||
spl_fold_insert(&type->type_fold, hash, id);
|
||||
tn->kind = SPL_TYPE_ERROR;
|
||||
return id;
|
||||
}
|
||||
|
||||
void spl_type_init(spl_type_t *type) {
|
||||
vec_init(type->type_table);
|
||||
spl_type_node_t type_node = {0};
|
||||
vec_push(type->type_table, type_node);
|
||||
spl_fold_init(&type->type_fold, type, spl_type_fold_eq, spl_type_fold_next,
|
||||
spl_type_fold_set_next);
|
||||
}
|
||||
|
||||
void spl_type_drop(spl_type_t *type) {
|
||||
for (usize i = 0; i < vec_size(type->type_table); i++) {
|
||||
spl_type_node_t *n = &vec_at(type->type_table, i);
|
||||
spl_type_node_free_vecs(n);
|
||||
}
|
||||
spl_fold_drop(&type->type_fold);
|
||||
vec_free(type->type_table);
|
||||
}
|
||||
|
||||
spl_type_node_t *spl_type_node(const spl_type_t *type, spl_type_id_t id) {
|
||||
if (!id || id >= vec_size(type->type_table))
|
||||
return NULL;
|
||||
return &vec_at(type->type_table, id);
|
||||
}
|
||||
|
||||
bool spl_type_is_subtype(const spl_type_t *type, spl_type_id_t sub, spl_type_id_t super) {
|
||||
if (sub == 0 || super == 0)
|
||||
return false;
|
||||
if (sub == super)
|
||||
return true;
|
||||
|
||||
spl_type_node_t *nsub = spl_type_node(type, sub);
|
||||
spl_type_node_t *nsuper = spl_type_node(type, super);
|
||||
if (!nsub || !nsuper)
|
||||
return false;
|
||||
|
||||
if (nsub->kind == SPL_TYPE_NEVER)
|
||||
return true;
|
||||
if (nsuper->kind == SPL_TYPE_ANY)
|
||||
return true;
|
||||
|
||||
if (nsub->kind == SPL_TYPE_INT && nsuper->kind == SPL_TYPE_INT) {
|
||||
if (nsub->int_type.bits == 0)
|
||||
return true;
|
||||
if (nsub->int_type.is_signed == nsuper->int_type.is_signed) {
|
||||
return nsub->int_type.bits <= nsuper->int_type.bits;
|
||||
}
|
||||
if (!nsub->int_type.is_signed && nsuper->int_type.is_signed) {
|
||||
return nsuper->int_type.bits > nsub->int_type.bits;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (nsub->kind == SPL_TYPE_FLOAT && nsuper->kind == SPL_TYPE_FLOAT) {
|
||||
if (nsub->float_type.bits == 0)
|
||||
return true;
|
||||
return nsub->float_type.bits <= nsuper->float_type.bits;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void spl_type_pure_dump_rec(spl_type_t *type, spl_type_id_t id, spl_type_id_vec_t *stack) {
|
||||
for (usize i = 0; i < vec_size(*stack); i++) {
|
||||
if (vec_at(*stack, i) == id) {
|
||||
printf("<rec#%zu>", id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (vec_size(*stack) > 32) {
|
||||
printf("...");
|
||||
return;
|
||||
}
|
||||
vec_push(*stack, id);
|
||||
|
||||
spl_type_node_t *n = spl_type_node(type, id);
|
||||
if (!n) {
|
||||
printf("none");
|
||||
(void)vec_pop(*stack);
|
||||
return;
|
||||
}
|
||||
switch (n->kind) {
|
||||
case SPL_TYPE_ERROR:
|
||||
printf("error");
|
||||
break;
|
||||
case SPL_TYPE_UNIT:
|
||||
printf("unit");
|
||||
break;
|
||||
case SPL_TYPE_ANY:
|
||||
printf("any");
|
||||
break;
|
||||
case SPL_TYPE_NEVER:
|
||||
printf("never");
|
||||
break;
|
||||
case SPL_TYPE_INFER:
|
||||
printf("infer");
|
||||
break;
|
||||
case SPL_TYPE_FORALL:
|
||||
case SPL_TYPE_EXISTS:
|
||||
printf("quant");
|
||||
break;
|
||||
case SPL_TYPE_BOOL:
|
||||
printf("bool");
|
||||
break;
|
||||
case SPL_TYPE_INT:
|
||||
if (n->int_type.bits == 0)
|
||||
printf("comptime_int");
|
||||
else
|
||||
printf("%s%zu", n->int_type.is_signed ? "i" : "u", n->int_type.bits);
|
||||
break;
|
||||
case SPL_TYPE_FLOAT:
|
||||
if (n->float_type.bits == 0)
|
||||
printf("comptime_float");
|
||||
else
|
||||
printf("f%zu", n->float_type.bits);
|
||||
break;
|
||||
case SPL_TYPE_PTR:
|
||||
printf("*");
|
||||
spl_type_pure_dump_rec(type, n->ptr_type.pointee, stack);
|
||||
break;
|
||||
case SPL_TYPE_ARRAY:
|
||||
printf("[%zu]", n->array_type.len);
|
||||
spl_type_pure_dump_rec(type, n->array_type.element, stack);
|
||||
break;
|
||||
case SPL_TYPE_STRUCT:
|
||||
case SPL_TYPE_UNION:
|
||||
case SPL_TYPE_ENUM:
|
||||
case SPL_TYPE_SHAPE: {
|
||||
const char *kw = "struct";
|
||||
if (n->kind == SPL_TYPE_UNION)
|
||||
kw = "union";
|
||||
else if (n->kind == SPL_TYPE_ENUM)
|
||||
kw = "enum";
|
||||
else if (n->kind == SPL_TYPE_SHAPE)
|
||||
kw = "shape";
|
||||
printf("%s{", kw);
|
||||
for (usize i = 0; i < vec_size(n->agg_type.variants); i++) {
|
||||
if (i)
|
||||
printf(", ");
|
||||
spl_type_member_t *m = &n->agg_type.variants.data[i];
|
||||
printf("%s:", m->name ? m->name : "?");
|
||||
spl_type_pure_dump_rec(type, m->type_id, stack);
|
||||
}
|
||||
printf("}");
|
||||
break;
|
||||
}
|
||||
case SPL_TYPE_FN:
|
||||
printf("fn(");
|
||||
for (usize i = 0; i < vec_size(n->fn_type.params); i++) {
|
||||
if (i)
|
||||
printf(",");
|
||||
spl_type_pure_dump_rec(type, vec_at(n->fn_type.params, i), stack);
|
||||
}
|
||||
printf(")");
|
||||
spl_type_pure_dump_rec(type, n->fn_type.ret, stack);
|
||||
break;
|
||||
}
|
||||
|
||||
(void)vec_pop(*stack);
|
||||
}
|
||||
|
||||
void spl_type_pure_dump(spl_type_t *type, spl_type_id_t id) {
|
||||
spl_type_id_vec_t stack;
|
||||
vec_init(stack);
|
||||
spl_type_pure_dump_rec(type, id, &stack);
|
||||
vec_free(stack);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_fn_param_tid(spl_type_t *type, spl_type_id_t fn_tid, usize idx) {
|
||||
spl_type_node_t *n = spl_type_node(type, fn_tid);
|
||||
if (!n || n->kind != SPL_TYPE_FN || idx >= vec_size(n->fn_type.params))
|
||||
return 0;
|
||||
return vec_at(n->fn_type.params, idx);
|
||||
}
|
||||
|
||||
static const spl_type_member_t *spl_type_agg_member_find(spl_type_t *type, spl_type_id_t tid,
|
||||
const char *name, usize *out_idx) {
|
||||
spl_type_node_t *n = spl_type_node(type, tid);
|
||||
if (!n || !name)
|
||||
return NULL;
|
||||
if (n->kind == SPL_TYPE_STRUCT || n->kind == SPL_TYPE_UNION || n->kind == SPL_TYPE_ENUM ||
|
||||
n->kind == SPL_TYPE_SHAPE) {
|
||||
for (usize i = 0; i < vec_size(n->agg_type.variants); i++) {
|
||||
spl_type_member_t *m = &n->agg_type.variants.data[i];
|
||||
if (m->name && strcmp(m->name, name) == 0) {
|
||||
if (out_idx)
|
||||
*out_idx = i;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const spl_type_member_t *spl_type_agg_member(spl_type_t *type, spl_type_id_t tid,
|
||||
const char *name) {
|
||||
return spl_type_agg_member_find(type, tid, name, NULL);
|
||||
}
|
||||
|
||||
bool spl_type_agg_member_idx(spl_type_t *type, spl_type_id_t tid, const char *name,
|
||||
usize *out_idx) {
|
||||
return spl_type_agg_member_find(type, tid, name, out_idx) != NULL;
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_simple(spl_type_t *type, spl_type_node_kind_t kind) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = kind;
|
||||
tn.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_int(spl_type_t *type, usize bits, int is_signed) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = SPL_TYPE_INT;
|
||||
tn.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
tn.int_type.bits = bits;
|
||||
tn.int_type.is_signed = is_signed;
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_float(spl_type_t *type, usize bits) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = SPL_TYPE_FLOAT;
|
||||
tn.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
tn.float_type.bits = bits;
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_ptr(spl_type_t *type, spl_type_id_t pointee) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = SPL_TYPE_PTR;
|
||||
tn.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
tn.ptr_type.pointee = pointee;
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_array(spl_type_t *type, spl_type_id_t element, usize len) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = SPL_TYPE_ARRAY;
|
||||
tn.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
tn.array_type.element = element;
|
||||
tn.array_type.len = len;
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_fn(spl_type_t *type, spl_type_id_t ret, const spl_type_id_t *params,
|
||||
usize nparams) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = SPL_TYPE_FN;
|
||||
tn.layout.mode = SPL_TYPE_LAYOUT_AUTO;
|
||||
vec_init(tn.fn_type.params);
|
||||
for (usize i = 0; i < nparams; i++) {
|
||||
vec_push(tn.fn_type.params, params[i]);
|
||||
}
|
||||
tn.fn_type.ret = ret;
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
|
||||
spl_type_id_t spl_type_builder_agg(spl_type_t *type, spl_type_node_kind_t tk,
|
||||
spl_type_member_vec_t *variants, spl_type_id_t tag_type,
|
||||
spl_type_layout_t layout) {
|
||||
spl_type_node_t tn = {0};
|
||||
tn.kind = tk;
|
||||
tn.layout = layout;
|
||||
tn.agg_type.variants = *variants;
|
||||
vec_init(*variants);
|
||||
if (tk == SPL_TYPE_ENUM) {
|
||||
tn.agg_type.tag_type = tag_type;
|
||||
} else {
|
||||
tn.agg_type.tag_type = 0;
|
||||
}
|
||||
return spl_type_node_push(type, &tn);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#ifndef __SPL_TYPE_H__
|
||||
#define __SPL_TYPE_H__
|
||||
|
||||
#include "../stage0/include/utils.h"
|
||||
#include "spl_fold.h"
|
||||
|
||||
/* 类型 intern 用的哈希混合(FNV-1a 风格,顺序敏感)。
|
||||
hash 只用于选择桶;相等判定始终由结构比较完成。 */
|
||||
static inline usize spl_hash_mix(usize h, usize x) {
|
||||
h ^= x;
|
||||
h *= (usize)1099511628211ULL;
|
||||
return h;
|
||||
}
|
||||
|
||||
static inline usize spl_hash_mix_str(usize h, const char *s) {
|
||||
if (!s)
|
||||
return spl_hash_mix(h, 0);
|
||||
while (*s) {
|
||||
h = spl_hash_mix(h, (usize)(unsigned char)*s);
|
||||
s++;
|
||||
}
|
||||
return spl_hash_mix(h, 0); /* 终止符参与混合,避免名称拼接歧义 */
|
||||
}
|
||||
|
||||
static inline usize spl_hash_seed(void) { return (usize)14695981039346656037ULL; }
|
||||
|
||||
typedef usize spl_type_id_t;
|
||||
typedef VEC(spl_type_id_t) spl_type_id_vec_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_TYPE_ERROR,
|
||||
SPL_TYPE_NEVER, // bottom type
|
||||
SPL_TYPE_ANY, // top type
|
||||
SPL_TYPE_UNIT,
|
||||
SPL_TYPE_INFER,
|
||||
SPL_TYPE_BOOL,
|
||||
SPL_TYPE_INT,
|
||||
SPL_TYPE_FLOAT,
|
||||
SPL_TYPE_PTR,
|
||||
SPL_TYPE_ARRAY,
|
||||
SPL_TYPE_STRUCT,
|
||||
SPL_TYPE_UNION,
|
||||
SPL_TYPE_ENUM,
|
||||
SPL_TYPE_FN,
|
||||
SPL_TYPE_FORALL, // TODO delete
|
||||
SPL_TYPE_EXISTS, // TODO delete
|
||||
SPL_TYPE_SHAPE,
|
||||
} spl_type_node_kind_t;
|
||||
|
||||
typedef enum {
|
||||
SPL_TYPE_LAYOUT_AUTO,
|
||||
SPL_TYPE_LAYOUT_EXTERN_C,
|
||||
SPL_TYPE_LAYOUT_PACKED,
|
||||
} spl_type_layout_mode_t;
|
||||
|
||||
typedef struct {
|
||||
spl_type_layout_mode_t mode;
|
||||
usize fixed_align_bits;
|
||||
} spl_type_layout_t;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
spl_type_id_t type_id;
|
||||
usize ast_node_ref;
|
||||
} spl_type_member_t;
|
||||
typedef VEC(spl_type_member_t) spl_type_member_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_type_node_kind_t kind;
|
||||
spl_type_layout_t layout;
|
||||
union {
|
||||
struct {
|
||||
usize bits;
|
||||
int is_signed;
|
||||
} int_type;
|
||||
struct {
|
||||
usize bits;
|
||||
} float_type;
|
||||
struct {
|
||||
spl_type_id_t pointee;
|
||||
} ptr_type;
|
||||
struct {
|
||||
spl_type_id_t element;
|
||||
usize len;
|
||||
} array_type;
|
||||
struct {
|
||||
spl_type_member_vec_t variants;
|
||||
spl_type_id_t tag_type;
|
||||
} agg_type;
|
||||
struct {
|
||||
spl_type_id_vec_t params;
|
||||
spl_type_id_t ret;
|
||||
} fn_type;
|
||||
struct {
|
||||
} shape_type;
|
||||
};
|
||||
usize hash_next; /* 同 hash 桶内下一个类型 id;0 表示链尾。不参与相等比较。 */
|
||||
} spl_type_node_t;
|
||||
typedef VEC(spl_type_node_t) spl_type_node_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_type_node_vec_t type_table;
|
||||
spl_fold_t type_fold;
|
||||
} spl_type_t;
|
||||
|
||||
void spl_type_init(spl_type_t *type);
|
||||
void spl_type_drop(spl_type_t *type);
|
||||
|
||||
spl_type_node_t *spl_type_node(const spl_type_t *type, spl_type_id_t id);
|
||||
spl_type_id_t spl_type_node_push(spl_type_t *type, spl_type_node_t *type_node);
|
||||
|
||||
spl_type_id_t spl_type_builder_simple(spl_type_t *type, spl_type_node_kind_t kind);
|
||||
spl_type_id_t spl_type_builder_int(spl_type_t *type, usize bits, int is_signed);
|
||||
spl_type_id_t spl_type_builder_float(spl_type_t *type, usize bits);
|
||||
spl_type_id_t spl_type_builder_ptr(spl_type_t *type, spl_type_id_t pointee);
|
||||
spl_type_id_t spl_type_builder_array(spl_type_t *type, spl_type_id_t element, usize len);
|
||||
spl_type_id_t spl_type_builder_fn(spl_type_t *type, spl_type_id_t ret, const spl_type_id_t *params,
|
||||
usize nparams);
|
||||
spl_type_id_t spl_type_builder_agg(spl_type_t *type, spl_type_node_kind_t tk,
|
||||
spl_type_member_vec_t *variants, spl_type_id_t tag_type,
|
||||
spl_type_layout_t layout);
|
||||
|
||||
void spl_type_pure_dump(spl_type_t *type, spl_type_id_t id);
|
||||
|
||||
bool spl_type_is_subtype(const spl_type_t *type, spl_type_id_t sub, spl_type_id_t super);
|
||||
|
||||
spl_type_id_t spl_type_fn_param_tid(spl_type_t *type, spl_type_id_t fn_tid, usize idx);
|
||||
const spl_type_member_t *spl_type_agg_member(spl_type_t *type, spl_type_id_t tid, const char *name);
|
||||
bool spl_type_agg_member_idx(spl_type_t *type, spl_type_id_t tid, const char *name, usize *out_idx);
|
||||
|
||||
#endif /* __SPL_TYPE_H__ */
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
/* splc0.c - SPL compiler CLI (stage 1, 引导用)
|
||||
*
|
||||
* splc0 --dump tokens|ast|sema|ir|vm <file> dump 前端产物
|
||||
* splc0 <in> <out> 编译
|
||||
*/
|
||||
#define __SCC_LOG_IMPL_IMPORT_SRC__
|
||||
#include "../stage0/include/utils.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ir_opt.h"
|
||||
#include "vm_opt.h"
|
||||
|
||||
#include "spl_ast.h"
|
||||
#include "spl_ast2ir.h"
|
||||
#include "spl_diag.h"
|
||||
#include "spl_ir2vm.h"
|
||||
#include "spl_lexer.h"
|
||||
#include "spl_parser.h"
|
||||
#include "spl_sema.h"
|
||||
#include "spl_tok.h"
|
||||
|
||||
typedef enum {
|
||||
DUMP_NONE,
|
||||
DUMP_TOKEN,
|
||||
DUMP_AST,
|
||||
DUMP_SEMA,
|
||||
DUMP_IR,
|
||||
DUMP_VM,
|
||||
} dump_t;
|
||||
|
||||
typedef struct {
|
||||
spl_ast_t *ast;
|
||||
char *abs_path;
|
||||
char *src;
|
||||
} spl_driver_module_t;
|
||||
typedef VEC(spl_driver_module_t) spl_driver_module_vec_t;
|
||||
|
||||
typedef struct {
|
||||
spl_ast_t main_ast;
|
||||
spl_tok_vec_t main_toks;
|
||||
char *main_path;
|
||||
|
||||
spl_driver_module_vec_t modules;
|
||||
MAP(const char *, usize) path_to_idx;
|
||||
|
||||
spl_diag_t *diag;
|
||||
} spl_driver_t;
|
||||
|
||||
static char *read_file(const char *path, long *out_len) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "path `%s` ", path);
|
||||
perror("fopen");
|
||||
return NULL;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
char *buf = malloc((size_t)len + 1);
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
size_t nread = fread(buf, 1, (size_t)len, f);
|
||||
fclose(f);
|
||||
buf[nread] = '\0';
|
||||
if (out_len)
|
||||
*out_len = (long)nread;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* 注意:按值接收,内部 vec_free 释放与调用方共享的 data 指针。
|
||||
调用后原 vec 视为已消费,不要再使用。 */
|
||||
static void dump_tokens(spl_tok_vec_t toks) {
|
||||
printf("tokens got (%zu)\n", toks.size);
|
||||
for (usize i = 0; i < toks.size; i++) {
|
||||
const spl_tok_t *t = &toks.data[i];
|
||||
printf("[%s] %.*s (%zu:%zu)\n", spl_tok_type_name(t->type), (int)t->len, t->lexeme, t->line,
|
||||
t->col);
|
||||
}
|
||||
vec_free(toks);
|
||||
}
|
||||
|
||||
/* 折叠重复分隔符、解析 "." 与 ".." 路径分量,使同一模块的 import 解析结果唯一。
|
||||
就地重写,'\' 统一成 '/';保留前导分隔符(绝对路径)。
|
||||
".." 会弹出上一个分量;无分量可弹时按相对语义保留(绝对根则忽略)。 */
|
||||
static void normalize_import_path(char *p) {
|
||||
usize n = strlen(p);
|
||||
char *out = malloc(n + 1);
|
||||
if (!out)
|
||||
return;
|
||||
char *base = out;
|
||||
char *w = out;
|
||||
int has_root = (p[0] == '/' || p[0] == '\\');
|
||||
if (has_root)
|
||||
*w++ = '/';
|
||||
|
||||
char *r = p;
|
||||
while (*r) {
|
||||
while (*r == '/' || *r == '\\')
|
||||
r++;
|
||||
if (!*r)
|
||||
break;
|
||||
char *seg = r;
|
||||
while (*r && *r != '/' && *r != '\\')
|
||||
r++;
|
||||
usize len = (usize)(r - seg);
|
||||
if (len == 1 && seg[0] == '.')
|
||||
continue;
|
||||
if (len == 2 && seg[0] == '.' && seg[1] == '.') {
|
||||
if (w == base + (has_root ? 1 : 0)) {
|
||||
if (!has_root) {
|
||||
*w++ = '.';
|
||||
*w++ = '.';
|
||||
}
|
||||
} else {
|
||||
char *q = w;
|
||||
while (q > base && q[-1] != '/')
|
||||
q--;
|
||||
usize sl = (usize)(w - q);
|
||||
if (sl == 2 && q[0] == '.' && q[1] == '.') {
|
||||
*w++ = '.';
|
||||
*w++ = '.';
|
||||
} else {
|
||||
if (q > base && q[-1] == '/')
|
||||
q--;
|
||||
w = q;
|
||||
if (w == base && has_root)
|
||||
w = base + 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (w != base && w[-1] != '/')
|
||||
*w++ = '/';
|
||||
memcpy(w, seg, len);
|
||||
w += len;
|
||||
}
|
||||
*w = 0;
|
||||
strcpy(p, out);
|
||||
free(out);
|
||||
}
|
||||
|
||||
/* 把 import_path 拼接到 importing_file 所在目录。
|
||||
from_fname 为空时按相对 cwd 处理。绝对路径直接透传。 */
|
||||
static void resolve_relative(const char *from, const char *path, char *out, usize cap) {
|
||||
if (!from || !path) {
|
||||
if (cap)
|
||||
out[0] = 0;
|
||||
return;
|
||||
}
|
||||
if (path[0] == '/' || path[0] == '\\' || (path[0] && path[1] == ':')) {
|
||||
snprintf(out, cap, "%s", path);
|
||||
normalize_import_path(out);
|
||||
return;
|
||||
}
|
||||
const char *last_sep = NULL;
|
||||
for (const char *p = from; *p; ++p)
|
||||
if (*p == '/' || *p == '\\')
|
||||
last_sep = p;
|
||||
if (!last_sep) {
|
||||
snprintf(out, cap, "%s", path);
|
||||
normalize_import_path(out);
|
||||
return;
|
||||
}
|
||||
usize dir_len = (usize)(last_sep - from) + 1;
|
||||
snprintf(out, cap, "%.*s%s", (int)dir_len, from, path);
|
||||
normalize_import_path(out);
|
||||
}
|
||||
|
||||
static spl_ast_t *driver_load(void *user, const char *from_fname, const char *path,
|
||||
spl_diag_t *diag) {
|
||||
spl_driver_t *d = user;
|
||||
char abs[1024];
|
||||
resolve_relative(from_fname, path, abs, sizeof abs);
|
||||
|
||||
usize idx;
|
||||
if (map_get(d->path_to_idx, abs, &idx)) {
|
||||
spl_ast_t *a = vec_at(d->modules, idx).ast;
|
||||
if (!a) {
|
||||
spl_diag_error(diag, SPL_DIAG_KIND_CIRCULAR_DEP, NULL, "circular import: `%s`", abs);
|
||||
return NULL;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/* 先登记 ast=NULL 占位:若后续解析过程中又有 @import 指回本路径,
|
||||
会命中这里,判定为环。 */
|
||||
char *key = strdup(abs);
|
||||
idx = vec_size(d->modules);
|
||||
spl_driver_module_t placeholder = {.ast = NULL, .abs_path = key, .src = NULL};
|
||||
vec_push(d->modules, placeholder);
|
||||
map_put(d->path_to_idx, key, idx);
|
||||
|
||||
long len = 0;
|
||||
char *src = read_file(abs, &len);
|
||||
if (!src) {
|
||||
spl_diag_error(diag, SPL_DIAG_KIND_LEX_UNEXPECTED, NULL, "@import: cannot open `%s`", abs);
|
||||
return NULL;
|
||||
}
|
||||
(void)len;
|
||||
|
||||
/* key 同时用作 lexer 的 fname:token.fname 与 map key 共享一份 strdup,
|
||||
减少一次分配;cleanup 时只 free module.abs_path */
|
||||
spl_tok_vec_t toks = spl_lex(src, key);
|
||||
spl_ast_t *ast = malloc(sizeof *ast);
|
||||
spl_ast_init(ast, &toks);
|
||||
spl_parser_t parser;
|
||||
spl_parser_init(&parser);
|
||||
parser.diag = diag;
|
||||
spl_ast_parse(&parser, ast);
|
||||
spl_parser_drop(&parser);
|
||||
|
||||
vec_at(d->modules, idx).ast = ast;
|
||||
vec_at(d->modules, idx).src = src;
|
||||
return ast;
|
||||
}
|
||||
|
||||
static int compile_spl(const char *src, const char *fname, const char *outpath, int gen_debug,
|
||||
dump_t dump, int opt) {
|
||||
(void)gen_debug;
|
||||
|
||||
spl_diag_t diag;
|
||||
spl_diag_init(&diag, stderr);
|
||||
|
||||
spl_driver_t drv = {0};
|
||||
drv.diag = &diag;
|
||||
drv.main_path = strdup(fname);
|
||||
drv.main_toks = spl_lex(src, drv.main_path);
|
||||
spl_ast_init(&drv.main_ast, &drv.main_toks);
|
||||
map_init(drv.path_to_idx, MAP_HASH_STR, MAP_CMP_STR);
|
||||
vec_init(drv.modules);
|
||||
|
||||
int rc = 0;
|
||||
|
||||
/* dump_tokens 消费 drv.main_toks(共享 data),之后 main_ast.input 悬空,
|
||||
所以这条分支必须在 spl_ast_parse 之前、且不能再 drop main_ast。 */
|
||||
if (dump == DUMP_TOKEN) {
|
||||
dump_tokens(drv.main_toks);
|
||||
free(drv.main_path);
|
||||
map_free(drv.path_to_idx);
|
||||
spl_diag_drop(&diag);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spl_attr_t attr;
|
||||
spl_lang_type_t lang_type;
|
||||
spl_layout_target_t layout_target;
|
||||
spl_layout_t layout;
|
||||
spl_scope_t scope;
|
||||
spl_sema_t sema;
|
||||
spl_ast2ir_t a2ir;
|
||||
spl_ir_builder_t ir_builder;
|
||||
spl_ir2vm_t ir2vm;
|
||||
spl_ast_ptr_vec_t all_asts;
|
||||
|
||||
int attr_ok = 0, lang_type_ok = 0, layout_ok = 0, scope_ok = 0;
|
||||
int sema_ok = 0, a2ir_ok = 0, irb_ok = 0, ir2vm_ok = 0;
|
||||
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_PARSE);
|
||||
spl_parser_t parser;
|
||||
spl_parser_init(&parser);
|
||||
parser.diag = &diag;
|
||||
spl_ast_parse(&parser, &drv.main_ast);
|
||||
spl_parser_drop(&parser);
|
||||
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
|
||||
spl_diag_summary(&diag);
|
||||
rc = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
if (dump == DUMP_AST) {
|
||||
spl_ast_dump(&drv.main_ast, drv.main_ast.root);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
spl_attr_init(&attr);
|
||||
attr_ok = 1;
|
||||
spl_lang_type_init(&lang_type);
|
||||
lang_type_ok = 1;
|
||||
|
||||
/* 目标描述符:剥离目标机器细节,layout 是 size/align/offset 的唯一来源。 */
|
||||
memset(&layout_target, 0, sizeof layout_target);
|
||||
layout_target.endian = SPL_LITTLE_ENDIAN;
|
||||
layout_target.ptr_size = sizeof(void *);
|
||||
layout_target.ptr_align = sizeof(void *);
|
||||
spl_layout_init(&layout, &layout_target);
|
||||
layout_ok = 1;
|
||||
|
||||
spl_scope_init(&scope);
|
||||
scope_ok = 1;
|
||||
|
||||
spl_sema_init(&sema, &drv.main_ast, &lang_type, &layout, &scope, &attr);
|
||||
sema_ok = 1;
|
||||
spl_module_loader_t loader = {.load = driver_load, .user = &drv};
|
||||
sema.loader = &loader;
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_SEMA);
|
||||
sema.diag = &diag;
|
||||
spl_sema_run(&sema);
|
||||
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
|
||||
spl_diag_summary(&diag);
|
||||
rc = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
if (dump == DUMP_SEMA) {
|
||||
spl_sema_dump(&sema);
|
||||
spl_ast_dump(&drv.main_ast, drv.main_ast.root);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* 收集所有 ast:主文件在前,被 import 的文件按加载顺序在后。
|
||||
ast2ir 内部会 vec_free(all_asts)。 */
|
||||
vec_init(all_asts);
|
||||
vec_push(all_asts, &drv.main_ast);
|
||||
for (usize i = 0; i < vec_size(drv.modules); ++i)
|
||||
vec_push(all_asts, vec_at(drv.modules, i).ast);
|
||||
|
||||
spl_ir_builder_init(&ir_builder);
|
||||
irb_ok = 1;
|
||||
spl_ast2ir_init(&a2ir, &ir_builder, &layout, &sema);
|
||||
a2ir_ok = 1;
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_AST2IR);
|
||||
a2ir.diag = &diag;
|
||||
spl_ast2ir_run(&a2ir, all_asts);
|
||||
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
|
||||
spl_diag_summary(&diag);
|
||||
rc = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* IR 层优化 pass(默认关闭,--opt 开启)。 */
|
||||
if (opt)
|
||||
spl_ir_opt(&ir_builder.ir, &attr);
|
||||
|
||||
if (dump == DUMP_IR) {
|
||||
spl_ir_dump(&ir_builder.ir, &lang_type.ir, &attr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* 类型已全部就绪:解析 layout,供 ir2vm 的 size/align/offset 使用。 */
|
||||
spl_layout_resolve(&layout, &lang_type.ir);
|
||||
|
||||
spl_ir2vm_init(&ir2vm, &a2ir.ir->ir, &lang_type.ir, &layout, &attr, opt);
|
||||
ir2vm_ok = 1;
|
||||
spl_diag_set_stage(&diag, SPL_STAGE_IR2VM);
|
||||
ir2vm.diag = &diag;
|
||||
rc = spl_ir2vm_run(&ir2vm);
|
||||
if (spl_diag_has_error(&diag) || spl_diag_has_fatal(&diag)) {
|
||||
spl_diag_summary(&diag);
|
||||
goto cleanup;
|
||||
}
|
||||
if (dump == DUMP_VM) {
|
||||
spl_prog_dump(&ir2vm.prog);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* ir2vm 之后的字节码优化 pass(默认关闭,--opt 开启)。 */
|
||||
if (opt)
|
||||
spl_prog_opt(&ir2vm.prog);
|
||||
|
||||
spl_prog_store_to_file(outpath, &ir2vm.prog);
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
if (ir2vm_ok)
|
||||
spl_ir2vm_drop(&ir2vm);
|
||||
if (a2ir_ok)
|
||||
spl_ast2ir_drop(&a2ir);
|
||||
if (irb_ok)
|
||||
spl_ir_builder_drop(&ir_builder);
|
||||
if (sema_ok)
|
||||
spl_sema_drop(&sema);
|
||||
if (scope_ok)
|
||||
spl_scope_drop(&scope);
|
||||
if (layout_ok)
|
||||
spl_layout_drop(&layout);
|
||||
if (lang_type_ok)
|
||||
spl_lang_type_drop(&lang_type);
|
||||
if (attr_ok)
|
||||
spl_attr_drop(&attr);
|
||||
|
||||
spl_ast_drop(&drv.main_ast);
|
||||
for (usize i = 0; i < vec_size(drv.modules); ++i) {
|
||||
spl_ast_t *a = vec_at(drv.modules, i).ast;
|
||||
if (a) {
|
||||
spl_ast_drop(a);
|
||||
free(a);
|
||||
}
|
||||
free(vec_at(drv.modules, i).abs_path);
|
||||
free(vec_at(drv.modules, i).src);
|
||||
}
|
||||
vec_free(drv.modules);
|
||||
/* map 的 key 是 module 里 strdup 出的 abs_path,已随 module 释放 */
|
||||
map_free(drv.path_to_idx);
|
||||
free(drv.main_path);
|
||||
spl_diag_drop(&diag);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
LOG_FATAL("Usage: splc0 [--dump <flags>] <in> [out]");
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) {
|
||||
LOG_INFO("splc0 <in> <out> compile (.spl -> .sir, stage B)");
|
||||
LOG_INFO("splc0 --dump <flags> <file> dump: lex,ast,sema,ir,vm,none");
|
||||
return 0;
|
||||
}
|
||||
int opt = 0;
|
||||
for (int ai = 1; ai < argc; ai++)
|
||||
if (strcmp(argv[ai], "--opt") == 0)
|
||||
opt = 1;
|
||||
|
||||
int argi = 1;
|
||||
if (argi >= argc) {
|
||||
LOG_FATAL("Usage: splc0 [--dump <flags>] <in> [out]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
dump_t dump = DUMP_NONE;
|
||||
if (strcmp(argv[argi], "--dump") == 0) {
|
||||
if (argc < argi + 3) {
|
||||
LOG_INFO("splc0: --dump need <flags> <file>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *flags = argv[argi + 1];
|
||||
if (strcmp(flags, "lex") == 0) {
|
||||
dump = DUMP_TOKEN;
|
||||
} else if (strcmp(flags, "ast") == 0) {
|
||||
dump = DUMP_AST;
|
||||
} else if (strcmp(flags, "sema") == 0) {
|
||||
dump = DUMP_SEMA;
|
||||
} else if (strcmp(flags, "ir") == 0) {
|
||||
dump = DUMP_IR;
|
||||
} else if (strcmp(flags, "vm") == 0) {
|
||||
dump = DUMP_VM;
|
||||
} else if (strcmp(flags, "none") == 0) {
|
||||
dump = DUMP_NONE;
|
||||
} else {
|
||||
LOG_FATAL("splc0: --dump takes one of lex|ast|sema|ir|vm|none, got `%s`", flags);
|
||||
return 1;
|
||||
}
|
||||
argi += 2;
|
||||
}
|
||||
/* splc0 [-g] <in> <out> */
|
||||
if (argc < argi + 2) {
|
||||
LOG_FATAL("Usage: splc0 <in> <out> [-g] or splc0 --dump <flags> <file>\n");
|
||||
return 1;
|
||||
}
|
||||
int gen_debug = 0;
|
||||
while (argi < argc - 1) {
|
||||
if (strcmp(argv[argi], "-g") == 0) {
|
||||
gen_debug = 1;
|
||||
argi++;
|
||||
} else if (strcmp(argv[argi], "--opt") == 0) {
|
||||
opt = 1;
|
||||
argi++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (argc < argi + 2) {
|
||||
LOG_FATAL("Usage: splc0 <in> <out> [-g] [--opt] or splc0 --dump <flags> <file>\n");
|
||||
return 1;
|
||||
}
|
||||
long len;
|
||||
char *src = read_file(argv[argi], &len);
|
||||
if (!src)
|
||||
return 1;
|
||||
int rc = compile_spl(src, argv[argi], argv[argi + 1], gen_debug, dump, opt);
|
||||
free(src);
|
||||
return rc;
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/* vm_opt.c - 字节码(VM 层)优化 pass
|
||||
*
|
||||
* 从 stage0/spl_mcode.c 迁出:对已生成的 spl_prog_t 做 prog->prog 优化。
|
||||
* 默认不启用(由 splc0 --opt 驱动)。 */
|
||||
|
||||
#include "vm_opt.h"
|
||||
|
||||
#include "../stage0/include/core_map.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 消除“store 后立刻 load 同一槽位”的往返。
|
||||
*
|
||||
* 模式(5 条):laddr X; swap; store k; laddr X; load k
|
||||
* 变换(3 条):laddr X; pick 1; store k
|
||||
* [.., v] -> laddr X => [.., v, X]
|
||||
* -> pick 1 => [.., v, X, v]
|
||||
* -> store k => [.., v] (存 v 到 X,同时栈上保留 v)
|
||||
* 与原来 store 后再 load 的效果等价,但去掉 2 条指令与一次内存读。
|
||||
*
|
||||
* 会真的删除指令,因此必须重定位:相对跳转、函数表 address、
|
||||
* 以及作为函数指针的 `push ptr <addr>`。仅当被删/被改写的指令不是
|
||||
* 跳转目标或函数入口时才变换(已收集 mark)。 */
|
||||
static usize peep_hash_int(usize k) { return MAP_HASH_INT(k); }
|
||||
static int peep_cmp_int(usize a, usize b) { return MAP_CMP_INT(a, b); }
|
||||
|
||||
void spl_prog_peephole(spl_prog_t *prog) {
|
||||
if (!prog)
|
||||
return;
|
||||
usize n = vec_size(prog->insns);
|
||||
if (n < 5)
|
||||
return;
|
||||
spl_vm_ins_t *ins = prog->insns.data;
|
||||
|
||||
unsigned char *mark = (unsigned char *)calloc(n, 1); /* 跳转目标/函数入口,受保护 */
|
||||
unsigned char *rem = (unsigned char *)calloc(n, 1);
|
||||
if (!mark || !rem) {
|
||||
free(mark);
|
||||
free(rem);
|
||||
return;
|
||||
}
|
||||
|
||||
for (usize i = 0; i < n; i++) {
|
||||
spl_vm_ins_t *x = &ins[i];
|
||||
if (x->opcode == SPL_JMP || x->opcode == SPL_BZ || x->opcode == SPL_BNZ) {
|
||||
isize t = (isize)i + 1 + (isize)x->imm;
|
||||
if (t >= 0 && (usize)t < n)
|
||||
mark[(usize)t] = 1;
|
||||
}
|
||||
}
|
||||
vec_for(prog->funcs, fi) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, fi);
|
||||
if (f->address < n)
|
||||
mark[f->address] = 1;
|
||||
}
|
||||
|
||||
for (usize i = 0; i + 4 < n; i++) {
|
||||
spl_vm_ins_t *a = &ins[i];
|
||||
spl_vm_ins_t *b = &ins[i + 1];
|
||||
spl_vm_ins_t *c = &ins[i + 2];
|
||||
spl_vm_ins_t *d = &ins[i + 3];
|
||||
spl_vm_ins_t *e = &ins[i + 4];
|
||||
if (a->opcode == SPL_LADDR && b->opcode == SPL_SWAP && c->opcode == SPL_STORE &&
|
||||
d->opcode == SPL_LADDR && e->opcode == SPL_LOAD && a->imm == d->imm &&
|
||||
c->type == e->type) {
|
||||
if (mark[i + 1] || mark[i + 3] || mark[i + 4])
|
||||
continue;
|
||||
b->opcode = SPL_PICK;
|
||||
b->type = SPL_VOID;
|
||||
b->imm = 1;
|
||||
rem[i + 3] = 1;
|
||||
rem[i + 4] = 1;
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
usize kept = 0;
|
||||
for (usize i = 0; i < n; i++)
|
||||
if (!rem[i])
|
||||
kept++;
|
||||
if (kept == n) {
|
||||
free(mark);
|
||||
free(rem);
|
||||
return;
|
||||
}
|
||||
|
||||
spl_vm_ins_t *fresh = (spl_vm_ins_t *)malloc(sizeof(spl_vm_ins_t) * (kept ? kept : 1));
|
||||
usize *map = (usize *)malloc(sizeof(usize) * (n + 1)); /* old index -> new index */
|
||||
usize *old_of_new = (usize *)malloc(sizeof(usize) * (kept ? kept : 1));
|
||||
if (!fresh || !map || !old_of_new) {
|
||||
free(mark);
|
||||
free(rem);
|
||||
free(fresh);
|
||||
free(map);
|
||||
free(old_of_new);
|
||||
return;
|
||||
}
|
||||
|
||||
usize out = 0;
|
||||
for (usize i = 0; i < n; i++) {
|
||||
if (!rem[i]) {
|
||||
map[i] = out;
|
||||
old_of_new[out] = i;
|
||||
fresh[out] = ins[i];
|
||||
out++;
|
||||
}
|
||||
}
|
||||
map[n] = kept;
|
||||
for (usize i = n; i-- > 0;)
|
||||
if (rem[i])
|
||||
map[i] = map[i + 1];
|
||||
|
||||
/* 函数地址 old->new */
|
||||
MAP(usize, usize) addr_map;
|
||||
map_init(addr_map, peep_hash_int, peep_cmp_int);
|
||||
vec_for(prog->funcs, fi) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, fi);
|
||||
usize old_a = f->address;
|
||||
if (old_a < n) {
|
||||
usize new_a = map[old_a];
|
||||
map_put(addr_map, old_a, new_a);
|
||||
f->address = new_a;
|
||||
}
|
||||
}
|
||||
|
||||
/* 修正相对跳转与函数指针常量 */
|
||||
for (usize j = 0; j < kept; j++) {
|
||||
spl_vm_ins_t *x = &fresh[j];
|
||||
if (x->opcode == SPL_JMP || x->opcode == SPL_BZ || x->opcode == SPL_BNZ) {
|
||||
isize t = (isize)old_of_new[j] + 1 + (isize)x->imm;
|
||||
if (t >= 0 && (usize)t <= n) {
|
||||
usize nt = map[(usize)t];
|
||||
x->imm = (spl_vm_val_t)(isize)((isize)nt - (isize)(j + 1));
|
||||
}
|
||||
} else if (x->opcode == SPL_PUSH && x->type == SPL_PTR && x->imm != 0) {
|
||||
usize nv = 0;
|
||||
if (map_get(addr_map, (usize)x->imm, &nv))
|
||||
x->imm = nv;
|
||||
}
|
||||
}
|
||||
map_free(addr_map);
|
||||
|
||||
/* 指令删除后函数长度变化,重算 ninsns(函数按地址升序排列)。 */
|
||||
{
|
||||
usize nf = vec_size(prog->funcs);
|
||||
for (usize i = 0; i < nf; i++) {
|
||||
spl_vm_func_t *f = &vec_at(prog->funcs, i);
|
||||
usize next = (i + 1 < nf) ? vec_at(prog->funcs, i + 1).address : kept;
|
||||
f->ninsns = next > f->address ? (spl_vm_val_t)(next - f->address) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
free(prog->insns.data);
|
||||
prog->insns.data = fresh;
|
||||
prog->insns.size = kept;
|
||||
prog->insns.cap = kept;
|
||||
|
||||
free(mark);
|
||||
free(rem);
|
||||
free(map);
|
||||
free(old_of_new);
|
||||
}
|
||||
|
||||
/* 优化 pass 流水线(纯 prog->prog):当前 P1,后续可追加。 */
|
||||
void spl_prog_opt(spl_prog_t *prog) { spl_prog_peephole(prog); }
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef __VM_OPT_H__
|
||||
#define __VM_OPT_H__
|
||||
|
||||
/* 字节码(VM 层)优化 pass:输入输出都是 spl_prog_t,纯函数式、可开关。 */
|
||||
|
||||
#include "../stage0/spl_mcode.h"
|
||||
|
||||
void spl_prog_peephole(spl_prog_t *prog);
|
||||
void spl_prog_opt(spl_prog_t *prog);
|
||||
|
||||
#endif /* __VM_OPT_H__ */
|
||||
+676
@@ -0,0 +1,676 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// AST:用 ADT(enum 带载荷)表达节点,节点引用是 arena 序号 usize。
|
||||
// stage1 的 C 结构只是「手写 union 模拟 ADT」;这里直接用 SPL 的 enum 载荷,
|
||||
// 不照抄 union 布局,只要求最终 dump / 语义与 stage1 一致。
|
||||
// 索引 0 为 NONE 哨兵(与 stage1 一致)。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
type .{ .Punct } = @import("token.spl");
|
||||
type BigInt = @import("utils/mp.spl");
|
||||
type .{ .Span, .span_none, .span_make, .span_is_none } = @import("./diag.spl");
|
||||
type .{ .Sources, .Source, .set_at } = @import("utils/source.spl");
|
||||
|
||||
// 算子不另立 enum:AST 直接存词法 Punct。一元/二元由节点 kind 区分
|
||||
// (一元:Sub=负 / Not=! / BitNot=~ / And=& / Mul=.*;二元即对应 Punct)。
|
||||
|
||||
// ---- 节点体(ADT;variant 名 = stage1 的 kind 名;载荷用内联匿名 struct)----
|
||||
|
||||
type Body = enum {
|
||||
None,
|
||||
Root: struct { members: usize },
|
||||
ContainerMembers: struct { external_tags: Vec, declarations: Vec },
|
||||
ExternalTagItem: struct { is_at: bool, is_external: bool, name: ByteView, args: Vec },
|
||||
TagItem: struct { is_at: bool, is_external: bool, name: ByteView, args: Vec },
|
||||
ComptimeDecl: struct { body: usize },
|
||||
TopLevelBlock: struct { tag_list: Vec, block: usize },
|
||||
FnDecl: struct {
|
||||
tag_list: Vec,
|
||||
is_comptime: bool,
|
||||
name: ByteView,
|
||||
params: Vec,
|
||||
ret_type: usize,
|
||||
body: usize,
|
||||
},
|
||||
VarDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize, init_expr: usize },
|
||||
ConstDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize, init_expr: usize },
|
||||
TypeDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize },
|
||||
MemberDecl: struct {
|
||||
attr_list: Vec,
|
||||
name: ByteView,
|
||||
type_expr: usize,
|
||||
init_expr: usize,
|
||||
},
|
||||
ParamDecl: struct { attr_list: Vec, name: ByteView, type_expr: usize },
|
||||
StatementRet: struct { expr: usize },
|
||||
StatementBreak: struct { label: usize, expr: usize },
|
||||
StatementContinue: struct { label: usize },
|
||||
StatementDefer: struct { body: usize },
|
||||
StatementComptime: struct { body: usize },
|
||||
StatementDecl: struct { decl: usize },
|
||||
StatementExpr: struct { expr: usize },
|
||||
AggregateMember: struct {
|
||||
attr_list: Vec,
|
||||
name: ByteView,
|
||||
type_expr: usize,
|
||||
init_expr: usize,
|
||||
},
|
||||
StructInitItem: struct { name: ByteView, expr: usize },
|
||||
MatchArm: struct { pattern: usize, body: usize },
|
||||
Label: struct { name: ByteView },
|
||||
ExprIntLit: struct { value: BigInt }, // 任意宽度(mp),解析后由 Ast 拥有
|
||||
ExprFloatLit: struct { text: ByteView },
|
||||
ExprCharLit: struct { value: u8 },
|
||||
ExprStringLit: struct { data: ByteView },
|
||||
ExprBoolLit: struct { value: bool },
|
||||
ExprNullLit,
|
||||
ExprUndefinedLit,
|
||||
ExprIdent: struct { name: ByteView },
|
||||
ExprUnary: struct { op: Punct, operand: usize },
|
||||
ExprBinary: struct { op: Punct, left: usize, right: usize },
|
||||
ExprCall: struct { callee: usize, args: Vec },
|
||||
ExprField: struct { obj: usize, name: ByteView },
|
||||
ExprIndex: struct { obj: usize, index: usize },
|
||||
ExprSlice: struct { obj: usize, begin: usize, end: usize },
|
||||
ExprCast: struct { expr: usize, type_expr: usize },
|
||||
ExprTuple: struct { items: Vec },
|
||||
ExprArrayLit: struct {
|
||||
has_len: bool,
|
||||
len: usize,
|
||||
is_underscore_len: bool,
|
||||
elem_type: usize,
|
||||
elems: Vec,
|
||||
},
|
||||
ExprAggregateLit: struct { name: ByteView, fields: Vec },
|
||||
ExprComptimeCall: struct { name: ByteView, args: Vec },
|
||||
ExprBlock: struct { stmts: Vec, tail_expr: usize },
|
||||
ExprIf: struct { cond: usize, then_block: usize, else_branch: usize },
|
||||
ExprIfvar: struct { var_decl: usize, then_block: usize, else_branch: usize },
|
||||
ExprWhile: struct { label: usize, cond: usize, body: usize, else_branch: usize },
|
||||
ExprLoop: struct { label: usize, body: usize },
|
||||
ExprFor: struct {
|
||||
label: usize,
|
||||
iter_exprs: Vec,
|
||||
pattern: usize,
|
||||
body: usize,
|
||||
else_branch: usize,
|
||||
},
|
||||
ExprMatch: struct { matched_expr: usize, arms: Vec },
|
||||
ExprFnType: struct { is_comptime: bool, params: Vec, ret_type: usize },
|
||||
TypePath: struct { name: ByteView, generic_args: Vec },
|
||||
TypePointer: struct { pointee: usize },
|
||||
TypeArray: struct {
|
||||
has_len: bool,
|
||||
len: isize,
|
||||
is_underscore_len: bool,
|
||||
elem_type: usize,
|
||||
},
|
||||
TypeSlice: struct { elem_type: usize },
|
||||
TypeFn: struct { is_comptime: bool, params: Vec, ret_type: usize },
|
||||
TypeParen: struct { types: Vec },
|
||||
TypeAggregate: struct { kind_name: ByteView, members: Vec },
|
||||
TypeComptimeCall: struct { name: ByteView, args: Vec },
|
||||
PatternIdent: struct { name: ByteView },
|
||||
PatternUnderscore,
|
||||
PatternDot: struct { name: ByteView, sub_pattern: usize },
|
||||
PatternTuple: struct { items: Vec },
|
||||
PatternList: struct { items: Vec },
|
||||
PatternAggregate: struct { items: Vec },
|
||||
PatternField: struct { name: ByteView, pattern: usize },
|
||||
ExprComptime: struct { body: usize },
|
||||
ExprComptimeInit: struct { name: ByteView, args: Vec, fields: Vec },
|
||||
}
|
||||
|
||||
fn kind_name(body: Body): []u8 {
|
||||
match body {
|
||||
.None => { ret "none"; },
|
||||
.Root(_) => { ret "root"; },
|
||||
.ContainerMembers(_) => { ret "container_members"; },
|
||||
.ExternalTagItem(_) => { ret "external_tag_item"; },
|
||||
.TagItem(_) => { ret "tag_item"; },
|
||||
.ComptimeDecl(_) => { ret "comptime_decl"; },
|
||||
.TopLevelBlock(_) => { ret "top_level_block"; },
|
||||
.FnDecl(_) => { ret "fn_decl"; },
|
||||
.VarDecl(_) => { ret "var_decl"; },
|
||||
.ConstDecl(_) => { ret "const_decl"; },
|
||||
.TypeDecl(_) => { ret "type_decl"; },
|
||||
.MemberDecl(_) => { ret "member_decl"; },
|
||||
.ParamDecl(_) => { ret "param_decl"; },
|
||||
.StatementRet(_) => { ret "statement_ret"; },
|
||||
.StatementBreak(_) => { ret "statement_break"; },
|
||||
.StatementContinue(_) => { ret "statement_continue"; },
|
||||
.StatementDefer(_) => { ret "statement_defer"; },
|
||||
.StatementComptime(_) => { ret "statement_comptime"; },
|
||||
.ExprComptime(_) => { ret "expr_comptime"; },
|
||||
.StatementDecl(_) => { ret "statement_decl"; },
|
||||
.StatementExpr(_) => { ret "statement_expr"; },
|
||||
.AggregateMember(_) => { ret "aggregate_member"; },
|
||||
.StructInitItem(_) => { ret "struct_init_item"; },
|
||||
.MatchArm(_) => { ret "match_arm"; },
|
||||
.Label(_) => { ret "label"; },
|
||||
.ExprIntLit(_) => { ret "int_lit"; },
|
||||
.ExprFloatLit(_) => { ret "float_lit"; },
|
||||
.ExprCharLit(_) => { ret "char_lit"; },
|
||||
.ExprStringLit(_) => { ret "string_lit"; },
|
||||
.ExprBoolLit(_) => { ret "bool_lit"; },
|
||||
.ExprNullLit => { ret "null_lit"; },
|
||||
.ExprUndefinedLit => { ret "undefined_lit"; },
|
||||
.ExprIdent(_) => { ret "ident"; },
|
||||
.ExprUnary(_) => { ret "unary"; },
|
||||
.ExprBinary(_) => { ret "binary"; },
|
||||
.ExprCall(_) => { ret "call"; },
|
||||
.ExprField(_) => { ret "field"; },
|
||||
.ExprIndex(_) => { ret "index"; },
|
||||
.ExprSlice(_) => { ret "slice"; },
|
||||
.ExprCast(_) => { ret "cast"; },
|
||||
.ExprTuple(_) => { ret "tuple"; },
|
||||
.ExprArrayLit(_) => { ret "array_literal"; },
|
||||
.ExprAggregateLit(_) => { ret "aggregate_literal"; },
|
||||
.ExprComptimeCall(_) => { ret "comptime_call"; },
|
||||
.ExprComptimeInit(_) => { ret "comptime_init"; },
|
||||
.ExprBlock(_) => { ret "block"; },
|
||||
.ExprIf(_) => { ret "if_expr"; },
|
||||
.ExprIfvar(_) => { ret "ifvar_expr"; },
|
||||
.ExprWhile(_) => { ret "while_expr"; },
|
||||
.ExprLoop(_) => { ret "loop_expr"; },
|
||||
.ExprFor(_) => { ret "for_expr"; },
|
||||
.ExprMatch(_) => { ret "match_expr"; },
|
||||
.ExprFnType(_) => { ret "fn_type"; },
|
||||
.TypePath(_) => { ret "type_path"; },
|
||||
.TypePointer(_) => { ret "type_pointer"; },
|
||||
.TypeArray(_) => { ret "type_array"; },
|
||||
.TypeSlice(_) => { ret "type_slice"; },
|
||||
.TypeFn(_) => { ret "type_fn"; },
|
||||
.TypeParen(_) => { ret "type_paren"; },
|
||||
.TypeAggregate(_) => { ret "type_aggregate"; },
|
||||
.TypeComptimeCall(_) => { ret "type_comptime_call"; },
|
||||
.PatternIdent(_) => { ret "pattern_ident"; },
|
||||
.PatternUnderscore => { ret "pattern_underscore"; },
|
||||
.PatternDot(_) => { ret "pattern_dot"; },
|
||||
.PatternTuple(_) => { ret "pattern_tuple"; },
|
||||
.PatternList(_) => { ret "pattern_list"; },
|
||||
.PatternAggregate(_) => { ret "pattern_aggregate"; },
|
||||
.PatternField(_) => { ret "pattern_field"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
type Node = struct {
|
||||
span: Span,
|
||||
resolved_def_id: usize,
|
||||
resolved_lang_tid: usize,
|
||||
expected_lang_tid: usize,
|
||||
attrs: Vec,
|
||||
body: Body,
|
||||
}
|
||||
|
||||
type Ast = @self;
|
||||
|
||||
nodes: Vec, // Node
|
||||
root: usize,
|
||||
sources: *Sources, // 借用;可为 null(dump 时不解析行列)
|
||||
strings: Vec, // ByteBuffer:已解码的字符串字面量字节(ByteView 指向其中)
|
||||
|
||||
type AstResult = enum {
|
||||
Ok: Ast,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new_node(body: Body, span: Span): Node {
|
||||
ret Node {
|
||||
.span = span,
|
||||
.resolved_def_id = 0,
|
||||
.resolved_lang_tid = 0,
|
||||
.expected_lang_tid = 0,
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.body = body,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(sources: *Sources): AstResult {
|
||||
var ast = Ast {
|
||||
.nodes = Vec.blank(@sizeof(Node), @alignof(Node)),
|
||||
.root = 0,
|
||||
.sources = sources,
|
||||
.strings = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
};
|
||||
var sentinel = new_node(.None, span_none());
|
||||
match Vec.push(&ast.nodes, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&ast.nodes);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(ast);
|
||||
}
|
||||
|
||||
fn drop(self: *Ast) {
|
||||
var i: usize = 0;
|
||||
while i < self.nodes.len {
|
||||
var n = node(self, i);
|
||||
if n != null {
|
||||
Vec.drop(&n.attrs);
|
||||
match n.body {
|
||||
.ExprIntLit(x) => { BigInt.drop(&x.value); },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.nodes);
|
||||
var k: usize = 0;
|
||||
while k < self.strings.len {
|
||||
match Vec.get(&self.strings, k) {
|
||||
.Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); },
|
||||
.Err(error) => {},
|
||||
}
|
||||
k = k + 1;
|
||||
}
|
||||
Vec.drop(&self.strings);
|
||||
self.root = 0;
|
||||
}
|
||||
|
||||
fn node(self: *Ast, ref: usize): *Node {
|
||||
if ref >= self.nodes.len { ret null; }
|
||||
ret (self.nodes.data as *Node) + ref;
|
||||
}
|
||||
|
||||
// 追加节点,返回其引用(失败返回 0)。
|
||||
fn alloc(self: *Ast, body: Body, span: Span): usize {
|
||||
var n = new_node(body, span);
|
||||
match Vec.push(&self.nodes, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&n.attrs);
|
||||
ret 0;
|
||||
},
|
||||
}
|
||||
ret self.nodes.len - 1;
|
||||
}
|
||||
|
||||
// 注册一段已解码的字符串字节(Ast 拥有;ByteView 指向其中,生命周期同 Ast)。
|
||||
// 空字符串也保证非空 data 指针(镜像 stage1:空字面量仍有稳定的非空字节地址)。
|
||||
fn push_string(self: *Ast, bytes: ByteBuffer): ByteView {
|
||||
var b = bytes;
|
||||
if b.storage.len == 0 {
|
||||
match ByteBuffer.reserve(&b, 1) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
@assert(b.storage.data != null, "ast string pool must be non-null");
|
||||
// 直接构造,避免 from_slice 把空串规范成 null 视图。
|
||||
var view = ByteView { .ptr = b.storage.data, .len = b.storage.len };
|
||||
match Vec.push(&self.strings, &b as *u8) {
|
||||
.Ok => { ret view; },
|
||||
.Err(error) => {
|
||||
ByteBuffer.drop(&b);
|
||||
ret ByteView.empty();
|
||||
},
|
||||
}
|
||||
ret ByteView.empty();
|
||||
}
|
||||
|
||||
fn count(self: *Ast): usize {
|
||||
ret self.nodes.len;
|
||||
}
|
||||
|
||||
fn ref_vec(): Vec {
|
||||
ret Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_ast_dump)----
|
||||
//
|
||||
// AST:
|
||||
// <indent>`-/|-kind #ref [line:col]
|
||||
// indent:栈上每层 last 为真输出 " ",否则 "| "。
|
||||
|
||||
fn push(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_byte(buf: *ByteBuffer, byte: u8): bool {
|
||||
match ByteBuffer.append_byte(buf, byte) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn ref_at(list: Vec, index: usize): usize {
|
||||
match Vec.get(&list, index) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn dump_opt(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): bool {
|
||||
if ref == 0 { ret true; }
|
||||
ret dump_node(ast, buf, stack, ref, last);
|
||||
}
|
||||
|
||||
fn dump_list(ast: *Ast, buf: *ByteBuffer, stack: *Vec, list: Vec): bool {
|
||||
var i: usize = 0;
|
||||
while i < list.len {
|
||||
var last = (i + 1 == list.len);
|
||||
if !dump_node(ast, buf, stack, ref_at(list, i), last) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): bool {
|
||||
var n = node(ast, ref);
|
||||
if n == null { ret false; }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < stack.len {
|
||||
var flag: u8 = 0;
|
||||
match Vec.get(stack, i) {
|
||||
.Ok(ptr) => { flag = ptr.*; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
if !push(buf, if flag != 0 { " " } else { "| " }) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
if !push(buf, if last { "`-" } else { "|-" }) { ret false; }
|
||||
if !push(buf, kind_name(n.body)) { ret false; }
|
||||
if !push(buf, " #") { ret false; }
|
||||
if !push_usize(buf, ref) { ret false; }
|
||||
if !span_is_none(n.span) && ast.sources != null {
|
||||
var src = set_at(ast.sources, n.span.file);
|
||||
if src != null {
|
||||
var pos = Source.line_col(src.*, n.span.start);
|
||||
if !push(buf, " [") { ret false; }
|
||||
if !push_usize(buf, pos.line) { ret false; }
|
||||
if !push_byte(buf, ':') { ret false; }
|
||||
if !push_usize(buf, pos.col) { ret false; }
|
||||
if !push_byte(buf, ']') { ret false; }
|
||||
}
|
||||
}
|
||||
if !push_byte(buf, '\n') { ret false; }
|
||||
|
||||
var l: u8 = 0;
|
||||
if last { l = 1; }
|
||||
match Vec.push(stack, &l as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
|
||||
var ok = true;
|
||||
match n.body {
|
||||
.None => {},
|
||||
.Root(r) => { ok = dump_opt(ast, buf, stack, r.members, true); },
|
||||
.ContainerMembers(c) => {
|
||||
ok = dump_list(ast, buf, stack, c.external_tags) &&
|
||||
dump_list(ast, buf, stack, c.declarations);
|
||||
},
|
||||
.ExternalTagItem(t) => { ok = dump_list(ast, buf, stack, t.args); },
|
||||
.TagItem(t) => { ok = dump_list(ast, buf, stack, t.args); },
|
||||
.ComptimeDecl(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.TopLevelBlock(t) => {
|
||||
ok = dump_list(ast, buf, stack, t.tag_list) &&
|
||||
dump_opt(ast, buf, stack, t.block, true);
|
||||
},
|
||||
.FnDecl(f) => {
|
||||
ok = dump_list(ast, buf, stack, f.tag_list) &&
|
||||
dump_list(ast, buf, stack, f.params) &&
|
||||
dump_opt(ast, buf, stack, f.ret_type, false) &&
|
||||
dump_opt(ast, buf, stack, f.body, true);
|
||||
},
|
||||
.VarDecl(d) => {
|
||||
ok = dump_list(ast, buf, stack, d.attr_list) &&
|
||||
dump_opt(ast, buf, stack, d.pattern, false) &&
|
||||
dump_opt(ast, buf, stack, d.type_expr, false) &&
|
||||
dump_opt(ast, buf, stack, d.init_expr, true);
|
||||
},
|
||||
.ConstDecl(d) => {
|
||||
ok = dump_list(ast, buf, stack, d.attr_list) &&
|
||||
dump_opt(ast, buf, stack, d.pattern, false) &&
|
||||
dump_opt(ast, buf, stack, d.type_expr, false) &&
|
||||
dump_opt(ast, buf, stack, d.init_expr, true);
|
||||
},
|
||||
.TypeDecl(d) => {
|
||||
ok = dump_list(ast, buf, stack, d.attr_list) &&
|
||||
dump_opt(ast, buf, stack, d.pattern, false) &&
|
||||
dump_opt(ast, buf, stack, d.type_expr, true);
|
||||
},
|
||||
.MemberDecl(d) => {
|
||||
ok = dump_list(ast, buf, stack, d.attr_list) &&
|
||||
dump_opt(ast, buf, stack, d.type_expr, false) &&
|
||||
dump_opt(ast, buf, stack, d.init_expr, true);
|
||||
},
|
||||
.ParamDecl(p) => {
|
||||
ok = dump_list(ast, buf, stack, p.attr_list) &&
|
||||
dump_opt(ast, buf, stack, p.type_expr, true);
|
||||
},
|
||||
.StatementRet(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
|
||||
.StatementBreak(b) => {
|
||||
ok = dump_opt(ast, buf, stack, b.label, false) &&
|
||||
dump_opt(ast, buf, stack, b.expr, true);
|
||||
},
|
||||
.StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); },
|
||||
.StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.ExprComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
|
||||
.StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); },
|
||||
.StatementExpr(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
|
||||
.AggregateMember(d) => {
|
||||
ok = dump_list(ast, buf, stack, d.attr_list) &&
|
||||
dump_opt(ast, buf, stack, d.type_expr, false) &&
|
||||
dump_opt(ast, buf, stack, d.init_expr, true);
|
||||
},
|
||||
.StructInitItem(s) => { ok = dump_opt(ast, buf, stack, s.expr, true); },
|
||||
.MatchArm(m) => {
|
||||
ok = dump_opt(ast, buf, stack, m.pattern, false) &&
|
||||
dump_opt(ast, buf, stack, m.body, true);
|
||||
},
|
||||
.Label(x) => {},
|
||||
.ExprIntLit(x) => {},
|
||||
.ExprFloatLit(x) => {},
|
||||
.ExprCharLit(x) => {},
|
||||
.ExprStringLit(x) => {},
|
||||
.ExprBoolLit(x) => {},
|
||||
.ExprNullLit => {},
|
||||
.ExprUndefinedLit => {},
|
||||
.ExprIdent(x) => {},
|
||||
.ExprUnary(u) => { ok = dump_opt(ast, buf, stack, u.operand, true); },
|
||||
.ExprBinary(b) => {
|
||||
ok = dump_opt(ast, buf, stack, b.left, false) &&
|
||||
dump_opt(ast, buf, stack, b.right, true);
|
||||
},
|
||||
.ExprCall(c) => {
|
||||
ok = dump_opt(ast, buf, stack, c.callee, false) &&
|
||||
dump_list(ast, buf, stack, c.args);
|
||||
},
|
||||
.ExprField(f) => { ok = dump_opt(ast, buf, stack, f.obj, true); },
|
||||
.ExprIndex(x) => {
|
||||
ok = dump_opt(ast, buf, stack, x.obj, false) &&
|
||||
dump_opt(ast, buf, stack, x.index, true);
|
||||
},
|
||||
.ExprSlice(s) => {
|
||||
ok = dump_opt(ast, buf, stack, s.obj, false) &&
|
||||
dump_opt(ast, buf, stack, s.begin, false) &&
|
||||
dump_opt(ast, buf, stack, s.end, true);
|
||||
},
|
||||
.ExprCast(c) => {
|
||||
ok = dump_opt(ast, buf, stack, c.expr, false) &&
|
||||
dump_opt(ast, buf, stack, c.type_expr, true);
|
||||
},
|
||||
.ExprTuple(v) => { ok = dump_list(ast, buf, stack, v.items); },
|
||||
.ExprArrayLit(a) => {
|
||||
ok = dump_opt(ast, buf, stack, a.elem_type, false) &&
|
||||
dump_list(ast, buf, stack, a.elems);
|
||||
},
|
||||
.ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); },
|
||||
.ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
|
||||
.ExprComptimeInit(x) => { ok = dump_list(ast, buf, stack, x.fields); },
|
||||
.ExprBlock(b) => {
|
||||
ok = dump_list(ast, buf, stack, b.stmts) &&
|
||||
dump_opt(ast, buf, stack, b.tail_expr, true);
|
||||
},
|
||||
.ExprIf(x) => {
|
||||
ok = dump_opt(ast, buf, stack, x.cond, false) &&
|
||||
dump_opt(ast, buf, stack, x.then_block, false) &&
|
||||
dump_opt(ast, buf, stack, x.else_branch, true);
|
||||
},
|
||||
.ExprIfvar(x) => {
|
||||
ok = dump_opt(ast, buf, stack, x.var_decl, false) &&
|
||||
dump_opt(ast, buf, stack, x.then_block, false) &&
|
||||
dump_opt(ast, buf, stack, x.else_branch, true);
|
||||
},
|
||||
.ExprWhile(w) => {
|
||||
ok = dump_opt(ast, buf, stack, w.label, false) &&
|
||||
dump_opt(ast, buf, stack, w.cond, false) &&
|
||||
dump_opt(ast, buf, stack, w.body, false) &&
|
||||
dump_opt(ast, buf, stack, w.else_branch, true);
|
||||
},
|
||||
.ExprLoop(x) => {
|
||||
ok = dump_opt(ast, buf, stack, x.label, false) &&
|
||||
dump_opt(ast, buf, stack, x.body, true);
|
||||
},
|
||||
.ExprFor(f) => {
|
||||
ok = dump_opt(ast, buf, stack, f.label, false) &&
|
||||
dump_list(ast, buf, stack, f.iter_exprs) &&
|
||||
dump_opt(ast, buf, stack, f.pattern, false) &&
|
||||
dump_opt(ast, buf, stack, f.body, false) &&
|
||||
dump_opt(ast, buf, stack, f.else_branch, true);
|
||||
},
|
||||
.ExprMatch(m) => {
|
||||
ok = dump_opt(ast, buf, stack, m.matched_expr, false) &&
|
||||
dump_list(ast, buf, stack, m.arms);
|
||||
},
|
||||
.ExprFnType(f) => {
|
||||
ok = dump_list(ast, buf, stack, f.params) &&
|
||||
dump_opt(ast, buf, stack, f.ret_type, true);
|
||||
},
|
||||
.TypePath(p) => {},
|
||||
.TypePointer(p) => { ok = dump_opt(ast, buf, stack, p.pointee, true); },
|
||||
.TypeArray(a) => { ok = dump_opt(ast, buf, stack, a.elem_type, true); },
|
||||
.TypeSlice(s) => { ok = dump_opt(ast, buf, stack, s.elem_type, true); },
|
||||
.TypeFn(f) => {
|
||||
ok = dump_list(ast, buf, stack, f.params) &&
|
||||
dump_opt(ast, buf, stack, f.ret_type, true);
|
||||
},
|
||||
.TypeParen(p) => { ok = dump_list(ast, buf, stack, p.types); },
|
||||
.TypeAggregate(a) => { ok = dump_list(ast, buf, stack, a.members); },
|
||||
.TypeComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
|
||||
.PatternIdent(x) => {},
|
||||
.PatternUnderscore => {},
|
||||
.PatternDot(p) => { ok = dump_opt(ast, buf, stack, p.sub_pattern, true); },
|
||||
.PatternTuple(v) => { ok = dump_list(ast, buf, stack, v.items); },
|
||||
.PatternList(v) => { ok = dump_list(ast, buf, stack, v.items); },
|
||||
.PatternAggregate(v) => { ok = dump_list(ast, buf, stack, v.items); },
|
||||
.PatternField(p) => { ok = dump_opt(ast, buf, stack, p.pattern, true); },
|
||||
}
|
||||
|
||||
stack.len = stack.len - 1;
|
||||
ret ok;
|
||||
}
|
||||
|
||||
// 把整棵树 dump 到 out(追加,不 clear)。
|
||||
fn dump(ast: *Ast, out: *ByteBuffer): bool {
|
||||
if !push(out, "AST:\n") { ret false; }
|
||||
var stack = Vec.blank(1, 1);
|
||||
defer { Vec.drop(&stack); }
|
||||
ret dump_node(ast, out, &stack, ast.root, true);
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_kind_names() {
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.None)), ByteView.from_slice("none")),
|
||||
"none name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ExprBinary(.{
|
||||
.op = .Add,
|
||||
.left = 0,
|
||||
.right = 0,
|
||||
}))),
|
||||
ByteView.from_slice("binary")),
|
||||
"binary name");
|
||||
}
|
||||
|
||||
fn test_alloc_and_payload() {
|
||||
var ast: Ast = undefined;
|
||||
match new(null) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => { @assert(false, "ast new failed"); },
|
||||
}
|
||||
defer { drop(&ast); }
|
||||
|
||||
@assert(count(&ast) == 1, "sentinel only");
|
||||
|
||||
var r = alloc(&ast, .ExprIdent(.{ .name = ByteView.from_slice("main") }), span_make(0, 5, 9));
|
||||
@assert(r == 1, "first alloc is 1");
|
||||
|
||||
var n = node(&ast, r);
|
||||
n.resolved_def_id = 99;
|
||||
@assert(n.span.file == 0 && n.span.start == 5 && n.span.end == 9, "span stored");
|
||||
@assert(n.resolved_def_id == 99, "header field");
|
||||
|
||||
match n.body {
|
||||
.ExprIdent(i) => {
|
||||
@assert(ByteView.eq(i.name, ByteView.from_slice("main")), "payload name");
|
||||
},
|
||||
_ => { @assert(false, "expected ident"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_bytes(buf: *ByteBuffer, expected: []u8) {
|
||||
var actual = ByteBuffer.as_slice(buf.*);
|
||||
@assert(actual.len == expected.len, "dump length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "dump byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_dump() {
|
||||
var ast: Ast = undefined;
|
||||
match new(null) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => { @assert(false, "ast new failed"); },
|
||||
}
|
||||
defer { drop(&ast); }
|
||||
|
||||
var id_ref = alloc(&ast, .ExprIdent(.{ .name = ByteView.from_slice("main") }), span_none()); // #1
|
||||
var decls = ref_vec();
|
||||
match Vec.push(&decls, &id_ref as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push decl"); },
|
||||
}
|
||||
var cm_ref = alloc(&ast, .ContainerMembers(.{
|
||||
.external_tags = ref_vec(),
|
||||
.declarations = decls,
|
||||
}), span_none()); // #2
|
||||
var root_ref = alloc(&ast, .Root(.{ .members = cm_ref }), span_none()); // #3
|
||||
ast.root = root_ref;
|
||||
|
||||
var buf = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&buf); }
|
||||
@assert(dump(&ast, &buf), "dump ok");
|
||||
// sources=null → 不解析行列;无 span 时也不打印。
|
||||
expect_bytes(&buf, "AST:\n`-root #3\n `-container_members #2\n `-ident #1\n");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_kind_names();
|
||||
test_alloc_and_payload();
|
||||
test_dump();
|
||||
}
|
||||
+2142
File diff suppressed because it is too large.
Load diff
+422
@@ -0,0 +1,422 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 属性注册表与属性节点(移植自 stage1/spl_attr.c)。注册表是数据:内置属性
|
||||
// 在 register_builtins 里登记,查找按 (ns, name)。节点记录命中信息 + 实参。
|
||||
// 新增属性只加注册项,不改 sema 逻辑(对应 SPL.md 的 @/# 扩展点)。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize, .append_i64 } = @import("utils/fmt.spl");
|
||||
|
||||
type AttrStage = enum {
|
||||
Sema,
|
||||
Layout,
|
||||
Ir,
|
||||
Opt,
|
||||
Backend,
|
||||
}
|
||||
|
||||
type AttrTarget = enum {
|
||||
Decl,
|
||||
Type,
|
||||
Expr,
|
||||
Stmt,
|
||||
}
|
||||
|
||||
type AttrDemand = enum {
|
||||
Optional,
|
||||
Required,
|
||||
}
|
||||
|
||||
type AttrInfo = struct {
|
||||
ns: ByteView,
|
||||
name: ByteView,
|
||||
stage: AttrStage,
|
||||
target: AttrTarget,
|
||||
demand: AttrDemand,
|
||||
min_args: i32,
|
||||
max_args: i32, // -1 = variadic
|
||||
}
|
||||
|
||||
type AttrArgKind = enum {
|
||||
Int,
|
||||
Str,
|
||||
Def,
|
||||
Type,
|
||||
}
|
||||
|
||||
type AttrArg = enum {
|
||||
Int: i64,
|
||||
Str: ByteView,
|
||||
Def: usize,
|
||||
Type: usize,
|
||||
}
|
||||
|
||||
type AttrNode = struct {
|
||||
info_index: usize, // 注册表下标;0 = 无
|
||||
args: Vec, // AttrArg
|
||||
consumed_mask: u32,
|
||||
}
|
||||
|
||||
type Attr = @self;
|
||||
|
||||
table: Vec, // AttrNode
|
||||
registry: Vec, // AttrInfo
|
||||
|
||||
type AttrResult = enum {
|
||||
Ok: Attr,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn empty_info(): AttrInfo {
|
||||
ret AttrInfo {
|
||||
.ns = ByteView.empty(),
|
||||
.name = ByteView.empty(),
|
||||
.stage = .Sema,
|
||||
.target = .Decl,
|
||||
.demand = .Optional,
|
||||
.min_args = 0,
|
||||
.max_args = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn new_node(): AttrNode {
|
||||
ret AttrNode {
|
||||
.info_index = 0,
|
||||
.args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)),
|
||||
.consumed_mask = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(): AttrResult {
|
||||
var at = Attr {
|
||||
.table = Vec.blank(@sizeof(AttrNode), @alignof(AttrNode)),
|
||||
.registry = Vec.blank(@sizeof(AttrInfo), @alignof(AttrInfo)),
|
||||
};
|
||||
var sentinel = new_node();
|
||||
match Vec.push(&at.table, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&at.table);
|
||||
Vec.drop(&at.registry);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
var reg_sentinel = empty_info();
|
||||
match Vec.push(&at.registry, ®_sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&at.table);
|
||||
Vec.drop(&at.registry);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
register_builtins(&at);
|
||||
ret .Ok(at);
|
||||
}
|
||||
|
||||
fn drop(self: *Attr) {
|
||||
var i: usize = 1;
|
||||
while i < self.table.len {
|
||||
var a = node(self, i);
|
||||
if a != null {
|
||||
Vec.drop(&a.args);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.table);
|
||||
Vec.drop(&self.registry);
|
||||
}
|
||||
|
||||
fn node(self: *Attr, id: usize): *AttrNode {
|
||||
if id == 0 || id >= self.table.len { ret null; }
|
||||
ret (self.table.data as *AttrNode) + id;
|
||||
}
|
||||
|
||||
fn info_at(self: *Attr, index: usize): *AttrInfo {
|
||||
if index == 0 || index >= self.registry.len { ret null; }
|
||||
match Vec.get(&self.registry, index) {
|
||||
.Ok(ptr) => { ret ptr as *AttrInfo; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn node_info(self: *Attr, id: usize): *AttrInfo {
|
||||
var a = node(self, id);
|
||||
if a == null { ret null; }
|
||||
ret info_at(self, a.info_index);
|
||||
}
|
||||
|
||||
// 追加属性节点(args 所有权转移)。
|
||||
fn alloc(self: *Attr, info_index: usize, args: Vec): usize {
|
||||
var a = new_node();
|
||||
a.info_index = info_index;
|
||||
a.args = args;
|
||||
match Vec.push(&self.table, &a as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.table.len - 1;
|
||||
}
|
||||
|
||||
fn registry_add(self: *Attr, info: AttrInfo) {
|
||||
match Vec.push(&self.registry, &info as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
}
|
||||
|
||||
// 返回注册表下标(>=1)或 0。
|
||||
fn lookup(self: *Attr, ns: ByteView, name: ByteView): usize {
|
||||
var i: usize = 1;
|
||||
while i < self.registry.len {
|
||||
var info = info_at(self, i);
|
||||
if info != null && ByteView.eq(info.ns, ns) && ByteView.eq(info.name, name) {
|
||||
ret i;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn make_info(ns: []u8, name: []u8, stage: AttrStage, target: AttrTarget, demand: AttrDemand,
|
||||
min_args: i32, max_args: i32): AttrInfo {
|
||||
ret AttrInfo {
|
||||
.ns = ByteView.from_slice(ns),
|
||||
.name = ByteView.from_slice(name),
|
||||
.stage = stage,
|
||||
.target = target,
|
||||
.demand = demand,
|
||||
.min_args = min_args,
|
||||
.max_args = max_args,
|
||||
};
|
||||
}
|
||||
|
||||
fn register_builtins(self: *Attr) {
|
||||
registry_add(self, make_info("", "extern", .Ir, .Decl, .Required, 1, 1));
|
||||
registry_add(self, make_info("", "inline", .Opt, .Decl, .Optional, 0, 0));
|
||||
registry_add(self, make_info("", "noinline", .Opt, .Decl, .Optional, 0, 0));
|
||||
registry_add(self, make_info("", "align", .Layout, .Type, .Required, 1, 1));
|
||||
registry_add(self, make_info("", "volatile", .Ir, .Expr, .Required, 0, 0));
|
||||
registry_add(self, make_info("", "atomic", .Ir, .Expr, .Required, 0, 1));
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_attr_dump / spl_attr_dump_id)----
|
||||
|
||||
fn push_out(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_hex(buf: *ByteBuffer, value: u32): bool {
|
||||
if !push_out(buf, "0x") { ret false; }
|
||||
match append_usize(buf, value as usize, 16, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn stage_name(s: AttrStage): []u8 {
|
||||
match s {
|
||||
.Sema => { ret "SEMA"; },
|
||||
.Layout => { ret "LAYOUT"; },
|
||||
.Ir => { ret "IR"; },
|
||||
.Opt => { ret "OPT"; },
|
||||
.Backend => { ret "BACKEND"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn target_name(t: AttrTarget): []u8 {
|
||||
match t {
|
||||
.Decl => { ret "DECL"; },
|
||||
.Type => { ret "TYPE"; },
|
||||
.Expr => { ret "EXPR"; },
|
||||
.Stmt => { ret "STMT"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn demand_name(d: AttrDemand): []u8 {
|
||||
match d {
|
||||
.Required => { ret "REQUIRED"; },
|
||||
.Optional => { ret "OPTIONAL"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn dump_id(self: *Attr, id: usize, out: *ByteBuffer): bool {
|
||||
if id == 0 {
|
||||
ret push_out(out, "?");
|
||||
}
|
||||
var a = node(self, id);
|
||||
if a == null { ret push_out(out, "?"); }
|
||||
var info = info_at(self, a.info_index);
|
||||
if info == null { ret push_out(out, "?"); }
|
||||
|
||||
if !push_out(out, ByteView.as_slice(info.name)) { ret false; }
|
||||
if a.args.len == 0 { ret true; }
|
||||
if !push_out(out, "(") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.args.len {
|
||||
if i > 0 && !push_out(out, ", ") { ret false; }
|
||||
var arg = arg_at(&a.args, i);
|
||||
if arg == null { ret false; }
|
||||
match arg.* {
|
||||
.Int(v) => {
|
||||
match append_i64(out, v, 10, false) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
},
|
||||
.Str(v) => {
|
||||
if !push_out(out, "\"") { ret false; }
|
||||
if !push_out(out, ByteView.as_slice(v)) { ret false; }
|
||||
if !push_out(out, "\"") { ret false; }
|
||||
},
|
||||
.Def(v) => {
|
||||
if !push_out(out, "def#") { ret false; }
|
||||
if !push_usize(out, v) { ret false; }
|
||||
},
|
||||
.Type(v) => {
|
||||
if !push_out(out, "type#") { ret false; }
|
||||
if !push_usize(out, v) { ret false; }
|
||||
},
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret push_out(out, ")");
|
||||
}
|
||||
|
||||
fn arg_at(v: *Vec, i: usize): *AttrArg {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret ptr as *AttrArg; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn dump(self: *Attr, out: *ByteBuffer): bool {
|
||||
if !push_out(out, "AttrTable:\n") { ret false; }
|
||||
var i: usize = 1;
|
||||
while i < self.table.len {
|
||||
var a = node(self, i);
|
||||
if !push_out(out, " #") { ret false; }
|
||||
if !push_usize(out, i) { ret false; }
|
||||
if !push_out(out, " ") { ret false; }
|
||||
if !dump_id(self, i, out) { ret false; }
|
||||
if a != null {
|
||||
var info = info_at(self, a.info_index);
|
||||
if info != null {
|
||||
if !push_out(out, " [stage=") { ret false; }
|
||||
if !push_out(out, stage_name(info.stage)) { ret false; }
|
||||
if !push_out(out, " target=") { ret false; }
|
||||
if !push_out(out, target_name(info.target)) { ret false; }
|
||||
if !push_out(out, " demand=") { ret false; }
|
||||
if !push_out(out, demand_name(info.demand)) { ret false; }
|
||||
if !push_out(out, " consumed=") { ret false; }
|
||||
if !push_hex(out, a.consumed_mask) { ret false; }
|
||||
if !push_out(out, "]") { ret false; }
|
||||
}
|
||||
}
|
||||
if !push_out(out, "\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn expect_text(buf: ByteBuffer, expected: []u8) {
|
||||
var got = ByteBuffer.as_slice(buf);
|
||||
@assert(got.len == expected.len, "dump length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(got.ptr[i] == expected.ptr[i], "dump byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_registry() {
|
||||
var at: Attr = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { at = v; },
|
||||
.Err(error) => { @assert(false, "attr new failed"); },
|
||||
}
|
||||
defer { drop(&at); }
|
||||
|
||||
@assert(at.registry.len == 7, "1 sentinel + 6 builtins");
|
||||
|
||||
var i = lookup(&at, ByteView.empty(), ByteView.from_slice("extern"));
|
||||
@assert(i != 0, "extern found");
|
||||
var info = info_at(&at, i);
|
||||
@assert(info != null, "extern info");
|
||||
@assert(info.min_args == 1 && info.max_args == 1, "extern arity");
|
||||
|
||||
var j = lookup(&at, ByteView.empty(), ByteView.from_slice("atomic"));
|
||||
@assert(j != 0, "atomic found");
|
||||
var jinfo = info_at(&at, j);
|
||||
@assert(jinfo.min_args == 0 && jinfo.max_args == 1, "atomic arity");
|
||||
|
||||
@assert(lookup(&at, ByteView.empty(), ByteView.from_slice("nope")) == 0, "unknown attr");
|
||||
}
|
||||
|
||||
fn test_alloc_and_dump_id() {
|
||||
var at: Attr = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { at = v; },
|
||||
.Err(error) => { @assert(false, "attr new failed"); },
|
||||
}
|
||||
defer { drop(&at); }
|
||||
|
||||
var idx = lookup(&at, ByteView.empty(), ByteView.from_slice("extern"));
|
||||
var args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg));
|
||||
var s: AttrArg = .Str(ByteView.from_slice("vm"));
|
||||
match Vec.push(&args, &s as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push str"); } }
|
||||
var n: AttrArg = .Int(3);
|
||||
match Vec.push(&args, &n as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push int"); } }
|
||||
var d: AttrArg = .Def(9);
|
||||
match Vec.push(&args, &d as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push def"); } }
|
||||
|
||||
var id = alloc(&at, idx, args);
|
||||
@assert(id == 1, "first attr id");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump_id(&at, id, &out) { @assert(false, "dump_id ok"); }
|
||||
expect_text(out, "extern(\"vm\", 3, def#9)");
|
||||
|
||||
var out2 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out2); }
|
||||
if !dump_id(&at, 0, &out2) { @assert(false, "dump_id none ok"); }
|
||||
expect_text(out2, "?");
|
||||
|
||||
var out3 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out3); }
|
||||
var l = lookup(&at, ByteView.empty(), ByteView.from_slice("inline"));
|
||||
var id2 = alloc(&at, l, Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)));
|
||||
if !dump(&at, &out3) { @assert(false, "dump ok"); }
|
||||
// 只校验首尾关键片段,避免依赖完整表格排版
|
||||
var got = ByteBuffer.as_slice(out3);
|
||||
@assert(got.len > 0, "dump nonempty");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_registry();
|
||||
test_alloc_and_dump_id();
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 声明句柄表(移植自 stage1/spl_lang_type.h 的 spl_def_node_t 部分)。
|
||||
// DefId 是「声明」的稳定身份:作用域符号、AST 节点 resolved_def_id、以及将来
|
||||
// comptime 生成的声明都用它。前向引用/环形引用由 state 状态机表达。
|
||||
//
|
||||
// 与 stage1 的差异:owner_ast 改为不透明 `*any`(借用),使本表不依赖具体 AST
|
||||
// 表示,便于将来 comptime/序列化复用;sema 侧自行 cast 回 *Ast。
|
||||
// 名称是 ByteView(借用源/AST),本表不做拥有。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
type .{ .Span, .span_none } = @import("./diag.spl");
|
||||
|
||||
type DefKind = enum {
|
||||
Error,
|
||||
Scalar,
|
||||
Member,
|
||||
Var,
|
||||
Fn,
|
||||
Agg,
|
||||
Distinct,
|
||||
Alias,
|
||||
Builtin,
|
||||
}
|
||||
|
||||
type DeclState = enum {
|
||||
Unresolved,
|
||||
Resolving,
|
||||
Resolved,
|
||||
Error,
|
||||
}
|
||||
|
||||
type DefNode = struct {
|
||||
kind: DefKind,
|
||||
name: ByteView,
|
||||
span: Span,
|
||||
lang_id: usize,
|
||||
owner: *any, // 借用;不透明,sema 侧 cast 回 *Ast
|
||||
ast_node_ref: usize,
|
||||
state: DeclState,
|
||||
owner_def: usize,
|
||||
}
|
||||
|
||||
type DefTable = @self;
|
||||
|
||||
table: Vec, // DefNode
|
||||
|
||||
type DefTableResult = enum {
|
||||
Ok: DefTable,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new_node(): DefNode {
|
||||
ret DefNode {
|
||||
.kind = .Error,
|
||||
.name = ByteView.empty(),
|
||||
.span = span_none(),
|
||||
.lang_id = 0,
|
||||
.owner = null,
|
||||
.ast_node_ref = 0,
|
||||
.state = .Unresolved,
|
||||
.owner_def = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(): DefTableResult {
|
||||
var dt = DefTable { .table = Vec.blank(@sizeof(DefNode), @alignof(DefNode)) };
|
||||
var sentinel = new_node();
|
||||
match Vec.push(&dt.table, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&dt.table);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(dt);
|
||||
}
|
||||
|
||||
fn drop(self: *DefTable) {
|
||||
Vec.drop(&self.table);
|
||||
}
|
||||
|
||||
// id 0 与越界均返回 null(对应 stage1 spl_def_node)。
|
||||
fn node(self: *DefTable, id: usize): *DefNode {
|
||||
if id == 0 || id >= self.table.len { ret null; }
|
||||
ret (self.table.data as *DefNode) + id;
|
||||
}
|
||||
|
||||
fn count(self: *DefTable): usize {
|
||||
ret self.table.len;
|
||||
}
|
||||
|
||||
// 追加一条声明,返回其 DefId(失败返回 0)。
|
||||
fn alloc(self: *DefTable, kind: DefKind, name: ByteView, span: Span, owner: *any,
|
||||
ast_node_ref: usize): usize {
|
||||
var n = new_node();
|
||||
n.kind = kind;
|
||||
n.name = name;
|
||||
n.span = span;
|
||||
n.owner = owner;
|
||||
n.ast_node_ref = ast_node_ref;
|
||||
match Vec.push(&self.table, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.table.len - 1;
|
||||
}
|
||||
|
||||
fn resolve(self: *DefTable, id: usize, lang_id: usize) {
|
||||
var d = node(self, id);
|
||||
if d == null { ret; }
|
||||
d.lang_id = lang_id;
|
||||
d.state = .Resolved;
|
||||
}
|
||||
|
||||
fn set_state(self: *DefTable, id: usize, state: DeclState) {
|
||||
var d = node(self, id);
|
||||
if d == null { ret; }
|
||||
d.state = state;
|
||||
}
|
||||
|
||||
fn get_lang_id(self: *DefTable, id: usize): usize {
|
||||
var d = node(self, id);
|
||||
if d == null { ret 0; }
|
||||
ret d.lang_id;
|
||||
}
|
||||
|
||||
// ---- 分类 ----
|
||||
|
||||
fn kind_name(kind: DefKind): []u8 {
|
||||
match kind {
|
||||
.Error => { ret "error"; },
|
||||
.Scalar => { ret "scalar"; },
|
||||
.Member => { ret "member"; },
|
||||
.Var => { ret "var"; },
|
||||
.Fn => { ret "fn"; },
|
||||
.Agg => { ret "agg"; },
|
||||
.Distinct => { ret "distinct"; },
|
||||
.Alias => { ret "alias"; },
|
||||
.Builtin => { ret "builtin"; },
|
||||
}
|
||||
ret "unknown";
|
||||
}
|
||||
|
||||
fn state_name(state: DeclState): []u8 {
|
||||
match state {
|
||||
.Unresolved => { ret "unresolved"; },
|
||||
.Resolving => { ret "resolving"; },
|
||||
.Resolved => { ret "resolved"; },
|
||||
.Error => { ret "error"; },
|
||||
}
|
||||
ret "unknown";
|
||||
}
|
||||
|
||||
// 能作为类型使用的 def(对应 stage1 sema_def_is_type)。
|
||||
fn is_type_kind(kind: DefKind): bool {
|
||||
match kind {
|
||||
.Scalar => { ret true; },
|
||||
.Agg => { ret true; },
|
||||
.Distinct => { ret true; },
|
||||
.Alias => { ret true; },
|
||||
.Builtin => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_type_def_dump 的文本)----
|
||||
|
||||
fn push_out(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump(self: *DefTable, id: usize, out: *ByteBuffer): bool {
|
||||
var d = node(self, id);
|
||||
if d == null {
|
||||
ret push_out(out, "none");
|
||||
}
|
||||
if !push_out(out, "kind=") { ret false; }
|
||||
if !push_out(out, kind_name(d.kind)) { ret false; }
|
||||
if !push_out(out, " lang_id=") { ret false; }
|
||||
if !push_usize(out, d.lang_id) { ret false; }
|
||||
if !push_out(out, " name=") { ret false; }
|
||||
if d.name.len == 0 {
|
||||
ret push_out(out, "?");
|
||||
}
|
||||
ret push_out(out, ByteView.as_slice(d.name));
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_alloc_and_state() {
|
||||
var dt: DefTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { dt = v; },
|
||||
.Err(error) => { @assert(false, "def table new failed"); },
|
||||
}
|
||||
defer { drop(&dt); }
|
||||
|
||||
@assert(count(&dt) == 1, "sentinel only");
|
||||
@assert(node(&dt, 0) == null, "id 0 is null");
|
||||
|
||||
var span = span_none();
|
||||
var a = alloc(&dt, .Fn, ByteView.from_slice("foo"), span, null, 7);
|
||||
var b = alloc(&dt, .Agg, ByteView.from_slice("Bar"), span, null, 9);
|
||||
@assert(a == 1 && b == 2, "ids increase from 1");
|
||||
|
||||
var na = node(&dt, a);
|
||||
@assert(na != null, "node a");
|
||||
@assert(ByteView.eq(na.name, ByteView.from_slice("foo")), "name stored");
|
||||
@assert(na.ast_node_ref == 7, "ast_node_ref stored");
|
||||
@assert(na.owner == null, "owner stored");
|
||||
match na.state {
|
||||
.Unresolved => {},
|
||||
_ => { @assert(false, "initial state unresolved"); },
|
||||
}
|
||||
match na.kind {
|
||||
.Fn => {},
|
||||
_ => { @assert(false, "kind fn"); },
|
||||
}
|
||||
|
||||
resolve(&dt, a, 42);
|
||||
@assert(get_lang_id(&dt, a) == 42, "lang id resolved");
|
||||
var na2 = node(&dt, a);
|
||||
match na2.state {
|
||||
.Resolved => {},
|
||||
_ => { @assert(false, "state resolved"); },
|
||||
}
|
||||
|
||||
set_state(&dt, b, .Resolving);
|
||||
var nb = node(&dt, b);
|
||||
match nb.state {
|
||||
.Resolving => {},
|
||||
_ => { @assert(false, "state resolving"); },
|
||||
}
|
||||
|
||||
@assert(get_lang_id(&dt, 999) == 0, "missing id");
|
||||
}
|
||||
|
||||
fn test_kind_helpers() {
|
||||
@assert(is_type_kind(.Scalar), "scalar is type");
|
||||
@assert(is_type_kind(.Agg), "agg is type");
|
||||
@assert(is_type_kind(.Alias), "alias is type");
|
||||
@assert(!is_type_kind(.Fn), "fn not type");
|
||||
@assert(!is_type_kind(.Var), "var not type");
|
||||
@assert(!is_type_kind(.Member), "member not type");
|
||||
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.Agg)), ByteView.from_slice("agg")), "agg name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(state_name(.Resolving)),
|
||||
ByteView.from_slice("resolving")),
|
||||
"resolving name");
|
||||
}
|
||||
|
||||
fn test_dump() {
|
||||
var dt: DefTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { dt = v; },
|
||||
.Err(error) => { @assert(false, "def table new failed"); },
|
||||
}
|
||||
defer { drop(&dt); }
|
||||
|
||||
var a = alloc(&dt, .Var, ByteView.from_slice("x"), span_none(), null, 0);
|
||||
resolve(&dt, a, 5);
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump(&dt, a, &out) { @assert(false, "dump ok"); }
|
||||
expect_text(out, "kind=var lang_id=5 name=x");
|
||||
|
||||
var out2 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out2); }
|
||||
if !dump(&dt, 0, &out2) { @assert(false, "dump none ok"); }
|
||||
expect_text(out2, "none");
|
||||
|
||||
var out3 = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out3); }
|
||||
var c = alloc(&dt, .Alias, ByteView.empty(), span_none(), null, 0);
|
||||
if !dump(&dt, c, &out3) { @assert(false, "dump empty name ok"); }
|
||||
expect_text(out3, "kind=alias lang_id=0 name=?");
|
||||
}
|
||||
|
||||
fn expect_text(buf: ByteBuffer, expected: []u8) {
|
||||
var got = ByteBuffer.as_slice(buf);
|
||||
@assert(got.len == expected.len, "dump length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(got.ptr[i] == expected.ptr[i], "dump byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_alloc_and_state();
|
||||
test_kind_helpers();
|
||||
test_dump();
|
||||
}
|
||||
+791
@@ -0,0 +1,791 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 丰富诊断(参考 rustc / Clang / Swift 的设计,不照搬 stage1 C 实现):
|
||||
// Severity Note/Help/Warning/Error/Fatal
|
||||
// Code 固定语义枚举 + 稳定 E 码;集中管理,利于 diff
|
||||
// Span 字节区间 {file,start,end};file 是 Sources 的下标,NO_FILE 表示无位置
|
||||
// Label 主/次标签(span + 文本),可多个
|
||||
// Child 子诊断(note/help,可带 span),挂在条目上
|
||||
// 渲染:人类可读(源码行 + caret 下划线)与 JSON(字段稳定)双格式。
|
||||
// 位置只存下标,渲染时经 Sources 解析行列/取行,避免复制文件名。
|
||||
// 消息按拥有型 Str 保存;条目拥有其 labels/children;clear/drop 释放全部内部资源。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("./utils/error.spl");
|
||||
type .{ .Vec } = @import("./utils/vec.spl");
|
||||
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
|
||||
type .{ .Str } = @import("./utils/str.spl");
|
||||
type .{ .ByteView } = @import("./utils/byte_view.spl");
|
||||
type .{ .append_usize } = @import("./utils/fmt.spl");
|
||||
type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl");
|
||||
|
||||
type Severity = enum {
|
||||
Note,
|
||||
Help,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
}
|
||||
|
||||
type Stage = enum {
|
||||
None,
|
||||
Lex,
|
||||
Parse,
|
||||
Sema,
|
||||
Lower,
|
||||
Vm,
|
||||
Driver,
|
||||
}
|
||||
|
||||
type Code = enum {
|
||||
None,
|
||||
// 词法 / 语法
|
||||
LexUnexpected,
|
||||
ParseExpected,
|
||||
ParseUnrecognized,
|
||||
// sema
|
||||
UndefinedSymbol,
|
||||
DuplicateSymbol,
|
||||
TypeMismatch,
|
||||
ArgCountMismatch,
|
||||
NotAFunction,
|
||||
NoField,
|
||||
NoMethod,
|
||||
IndexBaseInvalid,
|
||||
TupleIndexOob,
|
||||
FitsOverflow,
|
||||
CircularDep,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
UndefinedLabel,
|
||||
CastInvalid,
|
||||
UnknownAttr,
|
||||
UnknownIntrin,
|
||||
IntrinArgCount,
|
||||
VarUninitialized,
|
||||
CannotInfer,
|
||||
MustReturnValue,
|
||||
// lowering
|
||||
UnhandledNode,
|
||||
ImplicitCastFail,
|
||||
NoLvalue,
|
||||
BadFieldIndex,
|
||||
BadBlock,
|
||||
// vm
|
||||
IrUnknownKind,
|
||||
FrameOverflow,
|
||||
// 通用
|
||||
Internal,
|
||||
}
|
||||
|
||||
// file 下标不可用时的哨兵。
|
||||
fn no_file(): usize {
|
||||
ret ~(0 as usize);
|
||||
}
|
||||
|
||||
type Span = struct {
|
||||
file: usize,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
fn span_none(): Span {
|
||||
ret Span { .file = no_file(), .start = 0, .end = 0 };
|
||||
}
|
||||
|
||||
fn span_make(file: usize, start: usize, end: usize): Span {
|
||||
ret Span { .file = file, .start = start, .end = end };
|
||||
}
|
||||
|
||||
fn span_is_none(span: Span): bool {
|
||||
ret span.file == no_file();
|
||||
}
|
||||
|
||||
type Label = struct {
|
||||
span: Span,
|
||||
message: Str,
|
||||
primary: bool,
|
||||
}
|
||||
|
||||
type Child = struct {
|
||||
severity: Severity,
|
||||
span: Span,
|
||||
message: Str,
|
||||
}
|
||||
|
||||
type Item = struct {
|
||||
severity: Severity,
|
||||
stage: Stage,
|
||||
code: Code,
|
||||
span: Span,
|
||||
message: Str,
|
||||
labels: Vec, // Label
|
||||
children: Vec, // Child
|
||||
}
|
||||
|
||||
type Diag = @self;
|
||||
|
||||
items: Vec, // Item
|
||||
sources: *Sources, // 借用;可为 null(则只输出消息)
|
||||
errors: usize,
|
||||
warnings: usize,
|
||||
fatals: usize,
|
||||
|
||||
fn new(sources: *Sources): Diag {
|
||||
ret Diag {
|
||||
.items = Vec.blank(@sizeof(Item), @alignof(Item)),
|
||||
.sources = sources,
|
||||
.errors = 0,
|
||||
.warnings = 0,
|
||||
.fatals = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn item_at(self: *Diag, index: usize): *Item {
|
||||
match Vec.get(&self.items, index) {
|
||||
.Ok(ptr) => { ret ptr as *Item; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn label_at(it: *Item, index: usize): *Label {
|
||||
match Vec.get(&it.labels, index) {
|
||||
.Ok(ptr) => { ret ptr as *Label; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn child_at(it: *Item, index: usize): *Child {
|
||||
match Vec.get(&it.children, index) {
|
||||
.Ok(ptr) => { ret ptr as *Child; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn last(self: *Diag): usize {
|
||||
if self.items.len == 0 { ret no_file(); }
|
||||
ret self.items.len - 1;
|
||||
}
|
||||
|
||||
fn count(self: Diag): usize { ret self.items.len; }
|
||||
|
||||
fn error_count(self: Diag): usize { ret self.errors; }
|
||||
|
||||
fn warning_count(self: Diag): usize { ret self.warnings; }
|
||||
|
||||
fn fatal_count(self: Diag): usize { ret self.fatals; }
|
||||
|
||||
fn has_error(self: Diag): bool { ret self.errors > 0; }
|
||||
|
||||
fn has_fatal(self: Diag): bool { ret self.fatals > 0; }
|
||||
|
||||
// ---- 采集 ----
|
||||
|
||||
// 追加一条诊断,返回其下标(失败返回 no_file())。span 为主位置。
|
||||
fn emit(self: *Diag, severity: Severity, stage: Stage, code: Code, span: Span,
|
||||
msg: []u8): usize {
|
||||
var message = Str.new();
|
||||
match Str.from_slice(msg) {
|
||||
.Ok(v) => { message = v; },
|
||||
.Err(error) => { ret no_file(); },
|
||||
}
|
||||
var item = Item {
|
||||
.severity = severity,
|
||||
.stage = stage,
|
||||
.code = code,
|
||||
.span = span,
|
||||
.message = message,
|
||||
.labels = Vec.blank(@sizeof(Label), @alignof(Label)),
|
||||
.children = Vec.blank(@sizeof(Child), @alignof(Child)),
|
||||
};
|
||||
match Vec.push(&self.items, &item as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Str.drop(&item.message); ret no_file(); },
|
||||
}
|
||||
match severity {
|
||||
.Error => { self.errors = self.errors + 1; },
|
||||
.Fatal => { self.errors = self.errors + 1; self.fatals = self.fatals + 1; },
|
||||
.Warning => { self.warnings = self.warnings + 1; },
|
||||
_ => {},
|
||||
}
|
||||
ret self.items.len - 1;
|
||||
}
|
||||
|
||||
fn err(self: *Diag, span: Span, code: Code, msg: []u8): usize {
|
||||
ret emit(self, .Error, .None, code, span, msg);
|
||||
}
|
||||
|
||||
fn warn(self: *Diag, span: Span, code: Code, msg: []u8): usize {
|
||||
ret emit(self, .Warning, .None, code, span, msg);
|
||||
}
|
||||
|
||||
fn note(self: *Diag, span: Span, code: Code, msg: []u8): usize {
|
||||
ret emit(self, .Note, .None, code, span, msg);
|
||||
}
|
||||
|
||||
// 给最近一条诊断附加标签(primary 决定主/次)。
|
||||
fn attach_label(self: *Diag, span: Span, msg: []u8, primary: bool): Status {
|
||||
var idx = last(self);
|
||||
if idx == no_file() { ret .Err(make(.OutOfRange)); }
|
||||
var it = item_at(self, idx);
|
||||
if it == null { ret .Err(make(.OutOfRange)); }
|
||||
|
||||
var message = Str.new();
|
||||
match Str.from_slice(msg) {
|
||||
.Ok(v) => { message = v; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var lab = Label { .span = span, .message = message, .primary = primary };
|
||||
match Vec.push(&it.labels, &lab as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Str.drop(&lab.message); ret .Err(error); },
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 给最近一条诊断附加子诊断(note/help 等)。
|
||||
fn attach_child(self: *Diag, severity: Severity, span: Span, msg: []u8): Status {
|
||||
var idx = last(self);
|
||||
if idx == no_file() { ret .Err(make(.OutOfRange)); }
|
||||
var it = item_at(self, idx);
|
||||
if it == null { ret .Err(make(.OutOfRange)); }
|
||||
|
||||
var message = Str.new();
|
||||
match Str.from_slice(msg) {
|
||||
.Ok(v) => { message = v; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var ch = Child { .severity = severity, .span = span, .message = message };
|
||||
match Vec.push(&it.children, &ch as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Str.drop(&ch.message); ret .Err(error); },
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// ---- 名称 ----
|
||||
|
||||
fn severity_name(severity: Severity): []u8 {
|
||||
match severity {
|
||||
.Note => { ret "note"; },
|
||||
.Help => { ret "help"; },
|
||||
.Warning => { ret "warning"; },
|
||||
.Error => { ret "error"; },
|
||||
.Fatal => { ret "fatal"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn stage_name(stage: Stage): []u8 {
|
||||
match stage {
|
||||
.None => { ret "none"; },
|
||||
.Lex => { ret "lex"; },
|
||||
.Parse => { ret "parse"; },
|
||||
.Sema => { ret "sema"; },
|
||||
.Lower => { ret "lower"; },
|
||||
.Vm => { ret "vm"; },
|
||||
.Driver => { ret "driver"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
// 稳定 E 码(diff 依赖;新增 Code 时分配新码,勿改旧值)。
|
||||
fn code_str(code: Code): []u8 {
|
||||
match code {
|
||||
.None => { ret "E0000"; },
|
||||
.LexUnexpected => { ret "E0001"; },
|
||||
.ParseExpected => { ret "E0002"; },
|
||||
.ParseUnrecognized => { ret "E0003"; },
|
||||
.UndefinedSymbol => { ret "E0101"; },
|
||||
.DuplicateSymbol => { ret "E0102"; },
|
||||
.TypeMismatch => { ret "E0103"; },
|
||||
.ArgCountMismatch => { ret "E0104"; },
|
||||
.NotAFunction => { ret "E0105"; },
|
||||
.NoField => { ret "E0106"; },
|
||||
.NoMethod => { ret "E0107"; },
|
||||
.IndexBaseInvalid => { ret "E0108"; },
|
||||
.TupleIndexOob => { ret "E0109"; },
|
||||
.FitsOverflow => { ret "E0110"; },
|
||||
.CircularDep => { ret "E0111"; },
|
||||
.BreakOutsideLoop => { ret "E0112"; },
|
||||
.ContinueOutsideLoop => { ret "E0113"; },
|
||||
.UndefinedLabel => { ret "E0114"; },
|
||||
.CastInvalid => { ret "E0115"; },
|
||||
.UnknownAttr => { ret "E0116"; },
|
||||
.UnknownIntrin => { ret "E0117"; },
|
||||
.IntrinArgCount => { ret "E0118"; },
|
||||
.VarUninitialized => { ret "E0119"; },
|
||||
.CannotInfer => { ret "E0120"; },
|
||||
.MustReturnValue => { ret "E0121"; },
|
||||
.UnhandledNode => { ret "E0201"; },
|
||||
.ImplicitCastFail => { ret "E0202"; },
|
||||
.NoLvalue => { ret "E0203"; },
|
||||
.BadFieldIndex => { ret "E0204"; },
|
||||
.BadBlock => { ret "E0205"; },
|
||||
.IrUnknownKind => { ret "E0301"; },
|
||||
.FrameOverflow => { ret "E0302"; },
|
||||
.Internal => { ret "E9000"; },
|
||||
}
|
||||
ret "E0000";
|
||||
}
|
||||
|
||||
fn code_name(code: Code): []u8 {
|
||||
match code {
|
||||
.None => { ret "none"; },
|
||||
.LexUnexpected => { ret "lex_unexpected"; },
|
||||
.ParseExpected => { ret "parse_expected"; },
|
||||
.ParseUnrecognized => { ret "parse_unrecognized"; },
|
||||
.UndefinedSymbol => { ret "undefined_symbol"; },
|
||||
.DuplicateSymbol => { ret "duplicate_symbol"; },
|
||||
.TypeMismatch => { ret "type_mismatch"; },
|
||||
.ArgCountMismatch => { ret "arg_count_mismatch"; },
|
||||
.NotAFunction => { ret "not_a_function"; },
|
||||
.NoField => { ret "no_field"; },
|
||||
.NoMethod => { ret "no_method"; },
|
||||
.IndexBaseInvalid => { ret "index_base_invalid"; },
|
||||
.TupleIndexOob => { ret "tuple_index_oob"; },
|
||||
.FitsOverflow => { ret "fits_overflow"; },
|
||||
.CircularDep => { ret "circular_dep"; },
|
||||
.BreakOutsideLoop => { ret "break_outside_loop"; },
|
||||
.ContinueOutsideLoop => { ret "continue_outside_loop"; },
|
||||
.UndefinedLabel => { ret "undefined_label"; },
|
||||
.CastInvalid => { ret "cast_invalid"; },
|
||||
.UnknownAttr => { ret "unknown_attr"; },
|
||||
.UnknownIntrin => { ret "unknown_intrin"; },
|
||||
.IntrinArgCount => { ret "intrin_arg_count"; },
|
||||
.VarUninitialized => { ret "var_uninitialized"; },
|
||||
.CannotInfer => { ret "cannot_infer"; },
|
||||
.MustReturnValue => { ret "must_return_value"; },
|
||||
.UnhandledNode => { ret "unhandled_node"; },
|
||||
.ImplicitCastFail => { ret "implicit_cast_fail"; },
|
||||
.NoLvalue => { ret "no_lvalue"; },
|
||||
.BadFieldIndex => { ret "bad_field_index"; },
|
||||
.BadBlock => { ret "bad_block"; },
|
||||
.IrUnknownKind => { ret "ir_unknown_kind"; },
|
||||
.FrameOverflow => { ret "frame_overflow"; },
|
||||
.Internal => { ret "internal"; },
|
||||
}
|
||||
ret "none";
|
||||
}
|
||||
|
||||
// ---- 渲染:公共小工具 ----
|
||||
|
||||
fn append_num(out: *ByteBuffer, value: usize): Status {
|
||||
ret append_usize(out, value, 10, false);
|
||||
}
|
||||
|
||||
fn append_str(out: *ByteBuffer, s: *Str): Status {
|
||||
ret ByteBuffer.append(out, Str.as_slice(s));
|
||||
}
|
||||
|
||||
fn append_view(out: *ByteBuffer, view: ByteView): Status {
|
||||
ret ByteBuffer.append(out, ByteView.as_slice(view));
|
||||
}
|
||||
|
||||
type Resolved = struct {
|
||||
ok: bool,
|
||||
fname: ByteView,
|
||||
line: usize,
|
||||
col: usize,
|
||||
text: ByteView,
|
||||
width: usize,
|
||||
}
|
||||
|
||||
fn resolve(self: *Diag, span: Span): Resolved {
|
||||
var r = Resolved {
|
||||
.ok = false,
|
||||
.fname = ByteView.empty(),
|
||||
.line = 0,
|
||||
.col = 0,
|
||||
.text = ByteView.empty(),
|
||||
.width = 1,
|
||||
};
|
||||
if self.sources == null { ret r; }
|
||||
if span_is_none(span) { ret r; }
|
||||
|
||||
var src = set_at(self.sources, span.file);
|
||||
if src == null { ret r; }
|
||||
|
||||
var pos = Source.line_col(src.*, span.start);
|
||||
var width: usize = 1;
|
||||
if span.end > span.start { width = span.end - span.start; }
|
||||
r.ok = true;
|
||||
r.fname = src.fname;
|
||||
r.line = pos.line;
|
||||
r.col = pos.col;
|
||||
r.text = Source.line_text(src.*, pos.line);
|
||||
r.width = width;
|
||||
ret r;
|
||||
}
|
||||
|
||||
fn render_header(it: *Item, r: *Resolved, out: *ByteBuffer): Status {
|
||||
if r.ok {
|
||||
match append_view(out, r.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match ByteBuffer.append(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, "]: ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
ret ByteBuffer.append_byte(out, '\n');
|
||||
}
|
||||
|
||||
// 输出一行源码 + caret 下划线(列范围按字节;制表符不特殊处理)。
|
||||
fn render_span_visual(self: *Diag, span: Span, out: *ByteBuffer): Status {
|
||||
var r = resolve(self, span);
|
||||
if !r.ok { ret .Ok; }
|
||||
|
||||
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_view(out, r.text) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
|
||||
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
var pad: usize = 0;
|
||||
if r.col > 0 { pad = r.col - 1; }
|
||||
match ByteBuffer.append_repeat(out, ' ', pad) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
|
||||
var width = r.width;
|
||||
if width == 0 { width = 1; }
|
||||
if r.col > 0 {
|
||||
var room: usize = 0;
|
||||
if r.text.len >= pad { room = r.text.len - pad; }
|
||||
if room == 0 { room = 1; }
|
||||
if width > room { width = room; }
|
||||
}
|
||||
var k: usize = 0;
|
||||
while k < width {
|
||||
var ch: u8 = '~';
|
||||
if k == 0 { ch = '^'; }
|
||||
match ByteBuffer.append_byte(out, ch) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
k = k + 1;
|
||||
}
|
||||
ret ByteBuffer.append_byte(out, '\n');
|
||||
}
|
||||
|
||||
fn render_child_line(ch: *Child, out: *ByteBuffer): Status {
|
||||
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
ret ByteBuffer.append_byte(out, '\n');
|
||||
}
|
||||
|
||||
fn render_human(self: *Diag, out: *ByteBuffer): Status {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it == null { ret .Err(make(.OutOfRange)); }
|
||||
|
||||
var r = resolve(self, it.span);
|
||||
match render_header(it, &r, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match render_span_visual(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
|
||||
var j: usize = 0;
|
||||
while j < it.labels.len {
|
||||
var lab = label_at(it, j);
|
||||
if lab != null {
|
||||
match render_span_visual(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
if Str.size(lab.message) != 0 {
|
||||
match ByteBuffer.append(out, " ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
}
|
||||
j = j + 1;
|
||||
}
|
||||
|
||||
var c: usize = 0;
|
||||
while c < it.children.len {
|
||||
var ch = child_at(it, c);
|
||||
if ch != null {
|
||||
match render_child_line(ch, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match render_span_visual(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
c = c + 1;
|
||||
}
|
||||
|
||||
if i + 1 < self.items.len {
|
||||
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// ---- JSON 渲染(字段稳定,供 LSP / diff 工具)----
|
||||
|
||||
fn append_json_hex4(out: *ByteBuffer, b: u8): Status {
|
||||
var hex: []u8 = "0123456789abcdef";
|
||||
match ByteBuffer.append(out, "\\u00") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, hex[(b >> 4) as usize]) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
ret ByteBuffer.append_byte(out, hex[(b & 15) as usize]);
|
||||
}
|
||||
|
||||
fn append_json_str(out: *ByteBuffer, bytes: []u8): Status {
|
||||
match ByteBuffer.append_byte(out, '"') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
var i: usize = 0;
|
||||
while i < bytes.len {
|
||||
var b = bytes[i];
|
||||
if b == '"' {
|
||||
match ByteBuffer.append(out, "\\\"") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else if b == '\\' {
|
||||
match ByteBuffer.append(out, "\\\\") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else if b == '\n' {
|
||||
match ByteBuffer.append(out, "\\n") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else if b == '\r' {
|
||||
match ByteBuffer.append(out, "\\r") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else if b == '\t' {
|
||||
match ByteBuffer.append(out, "\\t") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else if b < ' ' {
|
||||
match append_json_hex4(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else {
|
||||
match ByteBuffer.append_byte(out, b) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret ByteBuffer.append_byte(out, '"');
|
||||
}
|
||||
|
||||
fn append_str_json(out: *ByteBuffer, s: *Str): Status {
|
||||
ret append_json_str(out, Str.as_slice(s));
|
||||
}
|
||||
|
||||
fn append_bool(out: *ByteBuffer, value: bool): Status {
|
||||
if value { ret ByteBuffer.append(out, "true"); }
|
||||
ret ByteBuffer.append(out, "false");
|
||||
}
|
||||
|
||||
fn append_loc_json(self: *Diag, span: Span, out: *ByteBuffer): Status {
|
||||
var r = resolve(self, span);
|
||||
match ByteBuffer.append(out, "{\"file\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
if r.ok {
|
||||
match append_json_str(out, ByteView.as_slice(r.fname)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else {
|
||||
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match ByteBuffer.append(out, ",\"start\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_num(out, span.start) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"end\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_num(out, span.end) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"line\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
if r.ok {
|
||||
match append_num(out, r.line) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else {
|
||||
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match ByteBuffer.append(out, ",\"col\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
if r.ok {
|
||||
match append_num(out, r.col) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
} else {
|
||||
match ByteBuffer.append(out, "null") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
ret ByteBuffer.append_byte(out, '}');
|
||||
}
|
||||
|
||||
fn render_item_json(self: *Diag, it: *Item, out: *ByteBuffer): Status {
|
||||
match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_json_str(out, severity_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"stage\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_json_str(out, stage_name(it.stage)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"code\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_json_str(out, code_str(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"name\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_json_str(out, code_name(it.code)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str_json(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
|
||||
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_loc_json(self, it.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
|
||||
match ByteBuffer.append(out, ",\"labels\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
var j: usize = 0;
|
||||
while j < it.labels.len {
|
||||
var lab = label_at(it, j);
|
||||
if lab != null {
|
||||
if j != 0 {
|
||||
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match ByteBuffer.append(out, "{\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str_json(out, &lab.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"primary\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_bool(out, lab.primary) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_loc_json(self, lab.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
j = j + 1;
|
||||
}
|
||||
match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
|
||||
match ByteBuffer.append(out, ",\"children\":[") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
var c: usize = 0;
|
||||
while c < it.children.len {
|
||||
var ch = child_at(it, c);
|
||||
if ch != null {
|
||||
if c != 0 {
|
||||
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match ByteBuffer.append(out, "{\"severity\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_json_str(out, severity_name(ch.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"message\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str_json(out, &ch.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ",\"span\":") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_loc_json(self, ch.span, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, '}') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
c = c + 1;
|
||||
}
|
||||
match ByteBuffer.append_byte(out, ']') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
ret ByteBuffer.append_byte(out, '}');
|
||||
}
|
||||
|
||||
fn render_json(self: *Diag, out: *ByteBuffer): Status {
|
||||
match ByteBuffer.append_byte(out, '[') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it == null { ret .Err(make(.OutOfRange)); }
|
||||
if i != 0 {
|
||||
match ByteBuffer.append_byte(out, ',') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match render_item_json(self, it, out) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
i = i + 1;
|
||||
}
|
||||
ret ByteBuffer.append_byte(out, ']');
|
||||
}
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
fn drop_item(it: *Item) {
|
||||
Str.drop(&it.message);
|
||||
var j: usize = 0;
|
||||
while j < it.labels.len {
|
||||
var lab = label_at(it, j);
|
||||
if lab != null { Str.drop(&lab.message); }
|
||||
j = j + 1;
|
||||
}
|
||||
Vec.drop(&it.labels);
|
||||
var c: usize = 0;
|
||||
while c < it.children.len {
|
||||
var ch = child_at(it, c);
|
||||
if ch != null { Str.drop(&ch.message); }
|
||||
c = c + 1;
|
||||
}
|
||||
Vec.drop(&it.children);
|
||||
}
|
||||
|
||||
fn clear(self: *Diag) {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it != null { drop_item(it); }
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.clear(&self.items);
|
||||
self.errors = 0;
|
||||
self.warnings = 0;
|
||||
self.fatals = 0;
|
||||
}
|
||||
|
||||
fn drop(self: *Diag) {
|
||||
clear(self);
|
||||
Vec.drop(&self.items);
|
||||
self.sources = null;
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn view(text: []u8): ByteView {
|
||||
ret ByteView.from_slice(text);
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected diag error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn has_bytes(out: *ByteBuffer, needle: []u8): bool {
|
||||
ret ByteView.contains(ByteView.from_slice(ByteBuffer.as_slice(out.*)), view(needle));
|
||||
}
|
||||
|
||||
fn test_collect_render_human() {
|
||||
var sources = set_new();
|
||||
match set_add(&sources, view("a.spl"), view("fn main() {\n y;\n}\n")) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => { @assert(false, "add source"); },
|
||||
}
|
||||
|
||||
var d = new(&sources);
|
||||
var idx = err(&d, span_make(0, 16, 17), .UndefinedSymbol, "undefined symbol `y`");
|
||||
@assert(idx != no_file(), "emit");
|
||||
expect_ok(attach_label(&d, span_make(0, 16, 17), "not found in scope", false));
|
||||
expect_ok(attach_child(&d, .Note, span_none(), "did you mean `x`?"));
|
||||
|
||||
@assert(count(d) == 1, "count");
|
||||
@assert(error_count(d) == 1 && has_error(d), "errors");
|
||||
@assert(warning_count(d) == 0 && !has_fatal(d), "warnings/fatal");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(render_human(&d, &out));
|
||||
@assert(has_bytes(&out, "a.spl:2:5: error[E0101]: undefined symbol `y`"), "human header");
|
||||
@assert(has_bytes(&out, " y;"), "human source line");
|
||||
@assert(has_bytes(&out, " ^"), "human caret");
|
||||
@assert(has_bytes(&out, "not found in scope"), "human label");
|
||||
@assert(has_bytes(&out, "note: did you mean `x`?"), "human child");
|
||||
ByteBuffer.drop(&out);
|
||||
|
||||
clear(&d);
|
||||
@assert(count(d) == 0 && error_count(d) == 0, "clear");
|
||||
drop(&d);
|
||||
set_drop(&sources);
|
||||
}
|
||||
|
||||
fn test_render_json() {
|
||||
var sources = set_new();
|
||||
match set_add(&sources, view("b.spl"), view("let z = 1;\n")) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => { @assert(false, "add source"); },
|
||||
}
|
||||
|
||||
var d = new(&sources);
|
||||
err(&d, span_make(0, 4, 5), .TypeMismatch, "expected i32, got bool");
|
||||
expect_ok(attach_child(&d, .Help, span_none(), "annotate the type"));
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(render_json(&d, &out));
|
||||
@assert(has_bytes(&out, "\"severity\":\"error\""), "json severity");
|
||||
@assert(has_bytes(&out, "\"code\":\"E0103\""), "json code");
|
||||
@assert(has_bytes(&out, "\"name\":\"type_mismatch\""), "json name");
|
||||
@assert(has_bytes(&out, "\"line\":1"), "json line");
|
||||
@assert(has_bytes(&out, "\"col\":5"), "json col");
|
||||
@assert(has_bytes(&out, "\"children\":[{\"severity\":\"help\""), "json child");
|
||||
ByteBuffer.drop(&out);
|
||||
|
||||
drop(&d);
|
||||
set_drop(&sources);
|
||||
}
|
||||
|
||||
fn test_empty_and_headerless() {
|
||||
var d = new(null);
|
||||
err(&d, span_none(), .Internal, "no location");
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(render_human(&d, &out));
|
||||
@assert(has_bytes(&out, "error[E9000]: no location"), "headerless");
|
||||
ByteBuffer.drop(&out);
|
||||
drop(&d);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_collect_render_human();
|
||||
test_render_json();
|
||||
test_empty_and_headerless();
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// comptime / const 求值的**接口 seam**(本轮只占位,不实现求值)。
|
||||
//
|
||||
// 目标:把「编译期求值」从 sema 主体里隔离出来,将来接一个解释器(可直接复用
|
||||
// VM 或 AST 求值器)时不动 sema 结构。sema 只在 comptime 语句 / const 初始化 /
|
||||
// @import 等点调用这里;未实现时统一返回 NotConst(= 放弃折叠,不报错)。
|
||||
//
|
||||
// 分块编译相关:eval 未来的产物是「编译期生成的声明」,统一经 DeclSink 登记,
|
||||
// 由驱动在固定点循环里插入并触发重跑;eval 自己不直接改 sema 表,避免重入。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type Ast = @import("./ast.spl");
|
||||
type Diag = @import("./diag.spl");
|
||||
|
||||
// 编译期值:占位。未来扩展为 BigInt / 聚合 / 类型句柄等。
|
||||
type EvalValue = enum {
|
||||
Int: usize, // TODO: BigInt
|
||||
Bool: bool,
|
||||
}
|
||||
|
||||
type EvalOutcome = enum {
|
||||
Value: EvalValue,
|
||||
NotConst, // 不是编译期常量(正常放弃,不产生诊断)
|
||||
Err: Error, // 求值出错(诊断由 eval 侧发出)
|
||||
}
|
||||
|
||||
// comptime 生成声明的出口:eval 只登记,插入/重跑由驱动负责。
|
||||
type DeclSink = struct {
|
||||
items: Vec, // usize:待插入的 AST 节点引用
|
||||
}
|
||||
|
||||
type DeclSinkResult = enum {
|
||||
Ok: DeclSink,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
// 求值上下文(借用 sink 与 diag;本轮不持有所有权)。
|
||||
type Eval = @self;
|
||||
|
||||
sink: *DeclSink, // 借用,可为 null
|
||||
diag: *Diag, // 借用,可为 null
|
||||
enabled: bool, // false = 完全不求值(等价旧行为)
|
||||
|
||||
// ---- DeclSink ----
|
||||
|
||||
fn sink_new(): DeclSinkResult {
|
||||
var s = DeclSink { .items = Vec.blank(@sizeof(usize), @alignof(usize)) };
|
||||
ret .Ok(s);
|
||||
}
|
||||
|
||||
fn sink_drop(self: *DeclSink) {
|
||||
Vec.drop(&self.items);
|
||||
}
|
||||
|
||||
fn sink_push(self: *DeclSink, node_ref: usize): bool {
|
||||
match Vec.push(&self.items, &node_ref as *u8) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn sink_len(self: *DeclSink): usize {
|
||||
ret self.items.len;
|
||||
}
|
||||
|
||||
// ---- Eval ----
|
||||
|
||||
fn new(sink: *DeclSink, diag: *Diag, enabled: bool): Eval {
|
||||
ret Eval {
|
||||
.sink = sink,
|
||||
.diag = diag,
|
||||
.enabled = enabled,
|
||||
};
|
||||
}
|
||||
|
||||
fn is_enabled(self: *Eval): bool {
|
||||
ret self.enabled;
|
||||
}
|
||||
|
||||
// 求值一个表达式。本轮一律 NotConst(可由调用方作为「放弃折叠」处理)。
|
||||
fn eval_expr(self: *Eval, ast: *Ast, ref: usize): EvalOutcome {
|
||||
if !self.enabled { ret .NotConst; }
|
||||
ret .NotConst;
|
||||
}
|
||||
|
||||
// 执行一个 comptime 块(可能产生声明,登记到 sink)。本轮不做。
|
||||
fn run_block(self: *Eval, ast: *Ast, ref: usize): EvalOutcome {
|
||||
if !self.enabled { ret .NotConst; }
|
||||
ret .NotConst;
|
||||
}
|
||||
|
||||
// 顶层 const 初始化求值。本轮不做。
|
||||
fn eval_const_init(self: *Eval, ast: *Ast, ref: usize): EvalOutcome {
|
||||
if !self.enabled { ret .NotConst; }
|
||||
ret .NotConst;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_sink() {
|
||||
var sink: DeclSink = undefined;
|
||||
match sink_new() {
|
||||
.Ok(v) => { sink = v; },
|
||||
.Err(error) => { @assert(false, "sink new failed"); },
|
||||
}
|
||||
defer { sink_drop(&sink); }
|
||||
|
||||
@assert(sink_len(&sink) == 0, "empty sink");
|
||||
@assert(sink_push(&sink, 42), "push");
|
||||
@assert(sink_len(&sink) == 1, "one item");
|
||||
}
|
||||
|
||||
fn test_stub_outcome() {
|
||||
var sink: DeclSink = undefined;
|
||||
match sink_new() {
|
||||
.Ok(v) => { sink = v; },
|
||||
.Err(error) => { @assert(false, "sink new failed"); },
|
||||
}
|
||||
defer { sink_drop(&sink); }
|
||||
|
||||
var ev = new(&sink, null, true);
|
||||
@assert(is_enabled(&ev), "enabled");
|
||||
var out = eval_expr(&ev, null, 0);
|
||||
match out {
|
||||
.NotConst => {},
|
||||
_ => { @assert(false, "stub returns NotConst"); },
|
||||
}
|
||||
|
||||
var ev2 = new(null, null, false);
|
||||
@assert(!is_enabled(&ev2), "disabled");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_sink();
|
||||
test_stub_outcome();
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 内建(intrinsic)注册表(移植自 stage1/spl_intrin.c)。表是数据:按 kind 查
|
||||
// 描述信息,按名字/字段/`as` 转换规则反查 kind。新增内建只改 info_at,
|
||||
// 不改 sema 逻辑(对应 SPL.md 的 @/# 扩展点)。
|
||||
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type .{ .TypeNode, .TypeEntry } = @import("./type_table.spl");
|
||||
type .{ .LangKind } = @import("./lang_type.spl");
|
||||
|
||||
// 文件命名空间(供跨模块用 IntrinOps.foo 调用)。
|
||||
type IntrinOps = @self;
|
||||
|
||||
type Intrin = enum {
|
||||
None,
|
||||
BitSizeof,
|
||||
Sizeof,
|
||||
Alignof,
|
||||
Assert,
|
||||
Breakpoint,
|
||||
Len,
|
||||
IntCast,
|
||||
Truncate,
|
||||
BitCast,
|
||||
FloatCast,
|
||||
Float2Int,
|
||||
Int2Float,
|
||||
Bool2Int,
|
||||
Ptr2Int,
|
||||
Int2Ptr,
|
||||
PtrCast,
|
||||
Enum2Int,
|
||||
OffsetOf,
|
||||
FieldCount,
|
||||
}
|
||||
|
||||
type IntrinForm = enum {
|
||||
Call,
|
||||
Field,
|
||||
}
|
||||
|
||||
type IntrinResult = enum {
|
||||
Unit,
|
||||
Bool,
|
||||
Usize,
|
||||
}
|
||||
|
||||
type IntrinInfo = struct {
|
||||
name: ByteView,
|
||||
kind: Intrin,
|
||||
form: IntrinForm,
|
||||
min_args: i32,
|
||||
max_args: i32, // -1 = variadic
|
||||
result: IntrinResult,
|
||||
}
|
||||
|
||||
fn form_is(a: IntrinForm, b: IntrinForm): bool {
|
||||
ret @enum2int(a) == @enum2int(b);
|
||||
}
|
||||
|
||||
fn kind_is(a: LangKind, b: LangKind): bool {
|
||||
ret @enum2int(a) == @enum2int(b);
|
||||
}
|
||||
|
||||
// 与 stage1 表逐项对应(枚举顺序即 kind 数值)。
|
||||
fn info_at(index: usize): IntrinInfo {
|
||||
match index {
|
||||
1 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@bitsizeof"), .kind = .BitSizeof,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
2 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@sizeof"), .kind = .Sizeof,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
3 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@alignof"), .kind = .Alignof,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
4 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@assert"), .kind = .Assert,
|
||||
.form = .Call, .min_args = 1, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
5 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@breakpoint"), .kind = .Breakpoint,
|
||||
.form = .Call, .min_args = 0, .max_args = 0, .result = .Unit };
|
||||
},
|
||||
6 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("len"), .kind = .Len,
|
||||
.form = .Field, .min_args = 0, .max_args = 0, .result = .Usize };
|
||||
},
|
||||
7 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@int_cast"), .kind = .IntCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
8 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@truncate"), .kind = .Truncate,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
9 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@bit_cast"), .kind = .BitCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
10 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@float_cast"), .kind = .FloatCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
11 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@float2int"), .kind = .Float2Int,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
12 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@int2float"), .kind = .Int2Float,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
13 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@bool2int"), .kind = .Bool2Int,
|
||||
.form = .Call, .min_args = 1, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
14 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@ptr2int"), .kind = .Ptr2Int,
|
||||
.form = .Call, .min_args = 1, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
15 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@int2ptr"), .kind = .Int2Ptr,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
16 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@ptr_cast"), .kind = .PtrCast,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Unit };
|
||||
},
|
||||
17 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@enum2int"), .kind = .Enum2Int,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
18 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@offsetof"), .kind = .OffsetOf,
|
||||
.form = .Call, .min_args = 2, .max_args = 2, .result = .Usize };
|
||||
},
|
||||
19 => {
|
||||
ret IntrinInfo { .name = ByteView.from_slice("@field_count"), .kind = .FieldCount,
|
||||
.form = .Call, .min_args = 1, .max_args = 1, .result = .Usize };
|
||||
},
|
||||
_ => {
|
||||
ret IntrinInfo { .name = ByteView.empty(), .kind = .None, .form = .Call,
|
||||
.min_args = 0, .max_args = 0, .result = .Unit };
|
||||
},
|
||||
}
|
||||
ret IntrinInfo { .name = ByteView.empty(), .kind = .None, .form = .Call,
|
||||
.min_args = 0, .max_args = 0, .result = .Unit };
|
||||
}
|
||||
|
||||
fn info(kind: Intrin): IntrinInfo {
|
||||
ret info_at(@enum2int(kind) as usize);
|
||||
}
|
||||
|
||||
fn table_count(): usize {
|
||||
ret 20;
|
||||
}
|
||||
|
||||
fn strip_at(name: ByteView): ByteView {
|
||||
if name.len > 0 && name.ptr[0] == '@' {
|
||||
ret ByteView { .ptr = name.ptr + 1, .len = name.len - 1 };
|
||||
}
|
||||
ret name;
|
||||
}
|
||||
|
||||
// 按名查找 CALL 形态的内建;忽略前导 '@'。
|
||||
fn lookup_call(name: ByteView): Intrin {
|
||||
var want = strip_at(name);
|
||||
var i: usize = 1;
|
||||
while i < table_count() {
|
||||
var d = info_at(i);
|
||||
if form_is(d.form, .Call) {
|
||||
var dn = strip_at(d.name);
|
||||
if ByteView.eq(dn, want) { ret d.kind; }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret .None;
|
||||
}
|
||||
|
||||
// 按接收者类型 + 字段名查找 FIELD 形态内建。
|
||||
fn lookup_field(recv: LangKind, field: ByteView): Intrin {
|
||||
if ByteView.eq(field, ByteView.from_slice("len")) {
|
||||
if kind_is(recv, .Array) || kind_is(recv, .Slice) { ret .Len; }
|
||||
}
|
||||
ret .None;
|
||||
}
|
||||
|
||||
fn is_int(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Int(x) => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn is_float(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Float(x) => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn is_bool(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Bool => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn is_ptr(e: *TypeEntry): bool {
|
||||
match e.node {
|
||||
.Ptr(x) => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn int_bits(e: *TypeEntry): usize {
|
||||
match e.node {
|
||||
.Int(x) => { ret x.bits; },
|
||||
_ => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// `as` 的展开规则表(改 as 语义只改这里)。
|
||||
fn lookup_cast(ir: *TypeTable, ft: usize, tt: usize): Intrin {
|
||||
var f = TypeTable.node(ir, ft);
|
||||
var t = TypeTable.node(ir, tt);
|
||||
if f == null || t == null { ret .None; }
|
||||
|
||||
if is_int(f) && is_int(t) {
|
||||
var fb = int_bits(f);
|
||||
var tb = int_bits(t);
|
||||
if fb == tb { ret .BitCast; }
|
||||
if fb > tb { ret .Truncate; }
|
||||
ret .IntCast;
|
||||
}
|
||||
if is_float(f) && is_float(t) { ret .FloatCast; }
|
||||
if is_bool(f) && is_int(t) { ret .Bool2Int; }
|
||||
if is_ptr(f) && is_int(t) { ret .Ptr2Int; }
|
||||
if is_int(f) && is_ptr(t) { ret .Int2Ptr; }
|
||||
if is_ptr(f) && is_ptr(t) { ret .PtrCast; }
|
||||
ret .None;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_info_table() {
|
||||
var d = info(.Sizeof);
|
||||
@assert(ByteView.eq(d.name, ByteView.from_slice("@sizeof")), "sizeof name");
|
||||
@assert(d.min_args == 1 && d.max_args == 1, "sizeof arity");
|
||||
@assert(form_is(d.form, .Call), "sizeof form");
|
||||
|
||||
var l = info(.Len);
|
||||
@assert(ByteView.eq(l.name, ByteView.from_slice("len")), "len name");
|
||||
@assert(form_is(l.form, .Field), "len form");
|
||||
}
|
||||
|
||||
fn test_lookup_call() {
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("sizeof"))) == @enum2int(Intrin.Sizeof),
|
||||
"sizeof by bare name");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("@sizeof"))) == @enum2int(Intrin.Sizeof),
|
||||
"sizeof by @name");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("assert"))) == @enum2int(Intrin.Assert),
|
||||
"assert");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("len"))) == @enum2int(Intrin.None),
|
||||
"len is field, not call");
|
||||
@assert(@enum2int(lookup_call(ByteView.from_slice("nope"))) == @enum2int(Intrin.None),
|
||||
"unknown");
|
||||
}
|
||||
|
||||
fn test_lookup_field() {
|
||||
@assert(@enum2int(lookup_field(.Slice, ByteView.from_slice("len"))) ==
|
||||
@enum2int(Intrin.Len),
|
||||
"slice len");
|
||||
@assert(@enum2int(lookup_field(.Array, ByteView.from_slice("len"))) ==
|
||||
@enum2int(Intrin.Len),
|
||||
"array len");
|
||||
@assert(@enum2int(lookup_field(.Struct, ByteView.from_slice("len"))) ==
|
||||
@enum2int(Intrin.None),
|
||||
"struct len");
|
||||
@assert(@enum2int(lookup_field(.Slice, ByteView.from_slice("ptr"))) ==
|
||||
@enum2int(Intrin.None),
|
||||
"slice ptr");
|
||||
}
|
||||
|
||||
fn test_lookup_cast() {
|
||||
var ir: TypeTable = undefined;
|
||||
match TypeTable.new() {
|
||||
.Ok(v) => { ir = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { TypeTable.drop(&ir); }
|
||||
|
||||
var i32t = TypeTable.make_int(&ir, 32, true);
|
||||
var i64t = TypeTable.make_int(&ir, 64, true);
|
||||
var f64t = TypeTable.make_float(&ir, 64);
|
||||
var boolt = TypeTable.make_bool(&ir);
|
||||
var pt = TypeTable.make_ptr(&ir, i32t);
|
||||
|
||||
@assert(@enum2int(lookup_cast(&ir, i64t, i32t)) == @enum2int(Intrin.Truncate),
|
||||
"truncate");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, i64t)) == @enum2int(Intrin.IntCast), "int cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, i32t)) == @enum2int(Intrin.BitCast), "bit cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, f64t, f64t)) == @enum2int(Intrin.FloatCast),
|
||||
"float cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, boolt, i32t)) == @enum2int(Intrin.Bool2Int),
|
||||
"bool2int");
|
||||
@assert(@enum2int(lookup_cast(&ir, pt, i32t)) == @enum2int(Intrin.Ptr2Int), "ptr2int");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, pt)) == @enum2int(Intrin.Int2Ptr), "int2ptr");
|
||||
@assert(@enum2int(lookup_cast(&ir, pt, pt)) == @enum2int(Intrin.PtrCast), "ptr cast");
|
||||
@assert(@enum2int(lookup_cast(&ir, i32t, f64t)) == @enum2int(Intrin.None),
|
||||
"int->float forbidden");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_info_table();
|
||||
test_lookup_call();
|
||||
test_lookup_field();
|
||||
test_lookup_cast();
|
||||
}
|
||||
+581
@@ -0,0 +1,581 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// SIR 中间表示(移植自 stage1/spl_ir.c/.h)。节点用 arena 序号引用(0 = error),
|
||||
// 按函数分桶。节点公共字段(kind/dbg/attrs)在外层 entry,kind 专属载荷在 ADT
|
||||
// IrBody;kind 与载荷变体一一对应(多种 kind 可共享同一载荷形状)。
|
||||
//
|
||||
// builder 维护当前函数/基本块/fixup 表,负责发射节点与回填跳转。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize, .append_i64 } = @import("utils/fmt.spl");
|
||||
type .{ .Span, .span_none } = @import("./diag.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type Attr = @import("./attr.spl");
|
||||
|
||||
type IrKind = enum {
|
||||
ArithAdd,
|
||||
ArithSub,
|
||||
ArithMul,
|
||||
ArithDiv,
|
||||
ArithRem,
|
||||
ArithNeg,
|
||||
ArithAbs,
|
||||
ArithAnd,
|
||||
ArithOr,
|
||||
ArithXor,
|
||||
ArithShl,
|
||||
ArithShr,
|
||||
ArithNot,
|
||||
CmpEq,
|
||||
CmpNe,
|
||||
CmpLt,
|
||||
CmpLe,
|
||||
CmpGt,
|
||||
CmpGe,
|
||||
CastTrunc,
|
||||
CastZext,
|
||||
CastSext,
|
||||
CastFext,
|
||||
CastFtrunc,
|
||||
CastBitcast,
|
||||
CastPtr2int,
|
||||
CastInt2ptr,
|
||||
CastBool2int,
|
||||
CaseInt2float,
|
||||
CaseFloat2int,
|
||||
MemAlloca,
|
||||
MemGlobalAlloc,
|
||||
MemLoad,
|
||||
MemStore,
|
||||
MemOffset,
|
||||
MemFieldPtr,
|
||||
MemCopy,
|
||||
MemSet,
|
||||
TypeConst,
|
||||
TypeBitsizeof,
|
||||
TypeSizeof,
|
||||
TypeAlignof,
|
||||
TypeOffsetof,
|
||||
TypeFieldCount,
|
||||
AggConstruct,
|
||||
AggExtract,
|
||||
AggInsert,
|
||||
ControlSelect,
|
||||
ControlBr,
|
||||
ControlJmp,
|
||||
ControlCall,
|
||||
ControlParam,
|
||||
ControlRet,
|
||||
ControlUnreachable,
|
||||
ControlTrap,
|
||||
DbgBreakpoint,
|
||||
DbgPanic,
|
||||
}
|
||||
|
||||
type ConstLit = enum {
|
||||
Bool: bool,
|
||||
Ptr: usize,
|
||||
Int: usize,
|
||||
Float: f64,
|
||||
Char: u8,
|
||||
Fn: usize,
|
||||
}
|
||||
|
||||
type IrBody = enum {
|
||||
None,
|
||||
Arith: struct { tid: usize, left: usize, right: usize },
|
||||
Cmp: struct { tid: usize, a: usize, b: usize },
|
||||
Cast: struct { from_tid: usize, to_tid: usize, val: usize },
|
||||
MemAlloc: struct { tid: usize, count: usize },
|
||||
MemGlobalAlloc: struct { tid: usize, const_node: usize },
|
||||
MemLoad: struct { tid: usize, ptr: usize },
|
||||
MemStore: struct { tid: usize, ptr: usize, val: usize },
|
||||
MemOffset: struct { tid: usize, ptr: usize, offset: usize },
|
||||
MemFieldPtr: struct { tid: usize, agg: usize, field_idx: usize },
|
||||
MemCopy: struct { tid: usize, dst: usize, src: usize, size: usize },
|
||||
MemSet: struct { tid: usize, dst: usize, val: usize, size: usize },
|
||||
Const: struct { tid: usize, lit: ConstLit },
|
||||
TypeOp: struct { tid: usize, field_idx: usize },
|
||||
AggConstruct: struct { tid: usize, fields: Vec },
|
||||
AggExtract: struct { tid: usize, field_tid: usize, field_idx: usize, val: usize },
|
||||
AggInsert: struct { tid: usize, field_idx: usize, agg: usize, field: usize },
|
||||
Select: struct { tid: usize, cond: usize, true_val: usize, false_val: usize },
|
||||
Br: struct { cond: usize, true_label: usize, false_label: usize },
|
||||
Jmp: struct { label: usize },
|
||||
Call: struct { tid: usize, func: usize, params: Vec },
|
||||
Param: struct { tid: usize, idx: usize },
|
||||
Ret: struct { tid: usize, val: usize },
|
||||
Panic: struct { msg: ByteView },
|
||||
}
|
||||
|
||||
type IrEntry = struct {
|
||||
kind: IrKind,
|
||||
dbg: Span,
|
||||
attrs: Vec, // attr id
|
||||
body: IrBody,
|
||||
}
|
||||
|
||||
type IrFunc = struct {
|
||||
name: ByteView,
|
||||
attrs: Vec, // attr id
|
||||
fn_tid: usize,
|
||||
nodes: Vec, // IrEntry
|
||||
}
|
||||
|
||||
type Ir = @self;
|
||||
|
||||
funcs: Vec, // IrFunc
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
fn new_entry(): IrEntry {
|
||||
ret IrEntry {
|
||||
.kind = .ArithAdd,
|
||||
.dbg = span_none(),
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.body = .None,
|
||||
};
|
||||
}
|
||||
|
||||
fn new(): Ir {
|
||||
var ir = Ir { .funcs = Vec.blank(@sizeof(IrFunc), @alignof(IrFunc)) };
|
||||
var sentinel = IrFunc {
|
||||
.name = ByteView.empty(),
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.fn_tid = 0,
|
||||
.nodes = Vec.blank(@sizeof(IrEntry), @alignof(IrEntry)),
|
||||
};
|
||||
var sn = new_entry();
|
||||
match Vec.push(&sentinel.nodes, &sn as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
match Vec.push(&ir.funcs, &sentinel as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
ret ir;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn drop_body(body: IrBody) {
|
||||
match body {
|
||||
.AggConstruct(a) => { Vec.drop(&a.fields); },
|
||||
.Call(c) => { Vec.drop(&c.params); },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(self: *Ir) {
|
||||
var i: usize = 0;
|
||||
while i < self.funcs.len {
|
||||
var f = (self.funcs.data as *IrFunc) + i;
|
||||
var j: usize = 0;
|
||||
while j < f.nodes.len {
|
||||
var e = (f.nodes.data as *IrEntry) + j;
|
||||
Vec.drop(&e.attrs);
|
||||
drop_body(e.body);
|
||||
j = j + 1;
|
||||
}
|
||||
Vec.drop(&f.attrs);
|
||||
Vec.drop(&f.nodes);
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.funcs);
|
||||
}
|
||||
|
||||
fn alloc_fn(self: *Ir): usize {
|
||||
var f = IrFunc {
|
||||
.name = ByteView.empty(),
|
||||
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.fn_tid = 0,
|
||||
.nodes = Vec.blank(@sizeof(IrEntry), @alignof(IrEntry)),
|
||||
};
|
||||
var sn = new_entry();
|
||||
match Vec.push(&f.nodes, &sn as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
match Vec.push(&self.funcs, &f as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
ret self.funcs.len - 1;
|
||||
}
|
||||
|
||||
fn alloc_node(self: *Ir, fn_id: usize): usize {
|
||||
if fn_id == 0 || fn_id >= self.funcs.len { ret 0; }
|
||||
var f = (self.funcs.data as *IrFunc) + fn_id;
|
||||
var n = new_entry();
|
||||
match Vec.push(&f.nodes, &n as *u8) { .Ok => {}, .Err(error) => { ret 0; } }
|
||||
ret f.nodes.len - 1;
|
||||
}
|
||||
|
||||
fn func(self: *Ir, fn_id: usize): *IrFunc {
|
||||
if fn_id == 0 || fn_id >= self.funcs.len { ret null; }
|
||||
ret (self.funcs.data as *IrFunc) + fn_id;
|
||||
}
|
||||
|
||||
fn node(self: *Ir, fn_id: usize, node_id: usize): *IrEntry {
|
||||
var f = func(self, fn_id);
|
||||
if f == null { ret null; }
|
||||
if node_id == 0 || node_id >= f.nodes.len { ret null; }
|
||||
ret (f.nodes.data as *IrEntry) + node_id;
|
||||
}
|
||||
|
||||
fn kind_name(kind: IrKind): []u8 {
|
||||
match kind {
|
||||
.ArithAdd => { ret "arith.add"; },
|
||||
.ArithSub => { ret "arith.sub"; },
|
||||
.ArithMul => { ret "arith.mul"; },
|
||||
.ArithDiv => { ret "arith.div"; },
|
||||
.ArithRem => { ret "arith.rem"; },
|
||||
.ArithNeg => { ret "arith.neg"; },
|
||||
.ArithAbs => { ret "arith.abs"; },
|
||||
.ArithAnd => { ret "arith.and"; },
|
||||
.ArithOr => { ret "arith.or"; },
|
||||
.ArithXor => { ret "arith.xor"; },
|
||||
.ArithShl => { ret "arith.shl"; },
|
||||
.ArithShr => { ret "arith.shr"; },
|
||||
.ArithNot => { ret "arith.not"; },
|
||||
.CmpEq => { ret "cmp.eq"; },
|
||||
.CmpNe => { ret "cmp.ne"; },
|
||||
.CmpLt => { ret "cmp.lt"; },
|
||||
.CmpLe => { ret "cmp.le"; },
|
||||
.CmpGt => { ret "cmp.gt"; },
|
||||
.CmpGe => { ret "cmp.ge"; },
|
||||
.CastTrunc => { ret "cast.trunc"; },
|
||||
.CastZext => { ret "cast.zext"; },
|
||||
.CastSext => { ret "cast.sext"; },
|
||||
.CastFext => { ret "cast.fext"; },
|
||||
.CastFtrunc => { ret "cast.ftrunc"; },
|
||||
.CastBitcast => { ret "cast.bitcast"; },
|
||||
.CastPtr2int => { ret "cast.ptr2int"; },
|
||||
.CastInt2ptr => { ret "cast.int2ptr"; },
|
||||
.CastBool2int => { ret "cast.bool2int"; },
|
||||
.CaseInt2float => { ret "case.int2float"; },
|
||||
.CaseFloat2int => { ret "case.float2int"; },
|
||||
.MemAlloca => { ret "mem.alloca"; },
|
||||
.MemGlobalAlloc => { ret "mem.global_alloc"; },
|
||||
.MemLoad => { ret "mem.load"; },
|
||||
.MemStore => { ret "mem.store"; },
|
||||
.MemOffset => { ret "mem.offset"; },
|
||||
.MemFieldPtr => { ret "mem.field"; },
|
||||
.MemCopy => { ret "mem.copy"; },
|
||||
.MemSet => { ret "mem.set"; },
|
||||
.TypeConst => { ret "type.const"; },
|
||||
.TypeBitsizeof => { ret "type.bitsizeof"; },
|
||||
.TypeSizeof => { ret "type.sizeof"; },
|
||||
.TypeAlignof => { ret "type.alignof"; },
|
||||
.TypeOffsetof => { ret "type.offsetof"; },
|
||||
.TypeFieldCount => { ret "type.field_count"; },
|
||||
.AggConstruct => { ret "agg.construct"; },
|
||||
.AggExtract => { ret "agg.extract"; },
|
||||
.AggInsert => { ret "agg.insert"; },
|
||||
.ControlSelect => { ret "control.select"; },
|
||||
.ControlBr => { ret "control.br"; },
|
||||
.ControlJmp => { ret "control.jmp"; },
|
||||
.ControlCall => { ret "control.call"; },
|
||||
.ControlParam => { ret "control.param"; },
|
||||
.ControlRet => { ret "control.ret"; },
|
||||
.ControlUnreachable => { ret "control.unreachable"; },
|
||||
.ControlTrap => { ret "control.trap"; },
|
||||
.DbgBreakpoint => { ret "dbg.breakpoint"; },
|
||||
.DbgPanic => { ret "dbg.panic"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
// ---- dump ----
|
||||
|
||||
fn out_str(out: *ByteBuffer, s: []u8): bool {
|
||||
match ByteBuffer.append(out, s) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn out_u(out: *ByteBuffer, v: usize): bool {
|
||||
match append_usize(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn out_i(out: *ByteBuffer, v: i64): bool {
|
||||
match append_i64(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump_type(out: *ByteBuffer, ty: *TypeTable, tid: usize): bool {
|
||||
if !out_str(out, "(") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, tid, out) { ret false; }
|
||||
ret out_str(out, ")");
|
||||
}
|
||||
|
||||
fn dump_ref(out: *ByteBuffer, ref: usize): bool {
|
||||
if ref == 0 { ret out_str(out, " _"); }
|
||||
if !out_str(out, " %") { ret false; }
|
||||
ret out_u(out, ref);
|
||||
}
|
||||
|
||||
fn dump_attrs(out: *ByteBuffer, attr: *Attr, ids: Vec): bool {
|
||||
if ids.len == 0 { ret true; }
|
||||
if !out_str(out, " attrs=[") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < ids.len {
|
||||
if i > 0 && !out_str(out, ", ") { ret false; }
|
||||
var id = list_at(ids, i);
|
||||
if !Attr.dump_id(attr, id, out) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret out_str(out, "]");
|
||||
}
|
||||
|
||||
fn list_at(v: Vec, i: usize): usize {
|
||||
if i >= v.len { ret 0; }
|
||||
ret ((v.data as *usize) + i).*;
|
||||
}
|
||||
|
||||
fn dump_body(out: *ByteBuffer, ty: *TypeTable, e: *IrEntry): bool {
|
||||
match e.body {
|
||||
.None => { ret true; },
|
||||
.Arith(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
if !dump_ref(out, a.left) { ret false; }
|
||||
// 一元 arith(neg/abs/not)不打印 right(镜像 stage1)。
|
||||
if @enum2int(e.kind) == @enum2int(IrKind.ArithNeg) ||
|
||||
@enum2int(e.kind) == @enum2int(IrKind.ArithAbs) ||
|
||||
@enum2int(e.kind) == @enum2int(IrKind.ArithNot) {
|
||||
ret true;
|
||||
}
|
||||
ret dump_ref(out, a.right);
|
||||
},
|
||||
.Cmp(c) => {
|
||||
if !dump_type(out, ty, c.tid) { ret false; }
|
||||
if !dump_ref(out, c.a) { ret false; }
|
||||
ret dump_ref(out, c.b);
|
||||
},
|
||||
.Cast(c) => {
|
||||
if !out_str(out, " ") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, c.from_tid, out) { ret false; }
|
||||
if !out_str(out, " -> ") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, c.to_tid, out) { ret false; }
|
||||
ret dump_ref(out, c.val);
|
||||
},
|
||||
.MemAlloc(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
ret dump_ref(out, m.count);
|
||||
},
|
||||
.MemGlobalAlloc(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !out_str(out, " g#") { ret false; }
|
||||
ret out_u(out, m.const_node);
|
||||
},
|
||||
.MemLoad(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
ret dump_ref(out, m.ptr);
|
||||
},
|
||||
.MemStore(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.ptr) { ret false; }
|
||||
ret dump_ref(out, m.val);
|
||||
},
|
||||
.MemOffset(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.ptr) { ret false; }
|
||||
ret dump_ref(out, m.offset);
|
||||
},
|
||||
.MemFieldPtr(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.agg) { ret false; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
ret out_u(out, m.field_idx);
|
||||
},
|
||||
.MemCopy(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.dst) { ret false; }
|
||||
if !dump_ref(out, m.src) { ret false; }
|
||||
ret dump_ref(out, m.size);
|
||||
},
|
||||
.MemSet(m) => {
|
||||
if !dump_type(out, ty, m.tid) { ret false; }
|
||||
if !dump_ref(out, m.dst) { ret false; }
|
||||
if !dump_ref(out, m.val) { ret false; }
|
||||
ret dump_ref(out, m.size);
|
||||
},
|
||||
.Const(c) => {
|
||||
if !dump_type(out, ty, c.tid) { ret false; }
|
||||
var te = TypeTable.node(ty, c.tid);
|
||||
var is_float = false;
|
||||
var is_fn = false;
|
||||
var is_u8 = false;
|
||||
var is_arr = false;
|
||||
if te != null {
|
||||
match te.node {
|
||||
.Float(x) => { is_float = true; },
|
||||
.Fn(x) => { is_fn = true; },
|
||||
.Array(x) => { is_arr = true; },
|
||||
.Int(x) => { if x.bits == 8 { is_u8 = true; } },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
if is_float {
|
||||
ret out_str(out, " float=?");
|
||||
}
|
||||
if is_arr {
|
||||
ret out_str(out, " bytes");
|
||||
}
|
||||
if is_fn {
|
||||
if !out_str(out, " fn#") { ret false; }
|
||||
match c.lit { .Fn(v) => { ret out_u(out, v); }, _ => { ret out_str(out, "?"); } }
|
||||
}
|
||||
if is_u8 {
|
||||
var cv: usize = 0;
|
||||
match c.lit {
|
||||
.Char(v) => { cv = v as usize; },
|
||||
.Int(v) => { cv = v; },
|
||||
_ => {},
|
||||
}
|
||||
if !out_str(out, " char='") { ret false; }
|
||||
match ByteBuffer.append_byte(out, cv as u8) { .Ok => {}, .Err(error) => { ret false; } }
|
||||
ret out_str(out, "'");
|
||||
}
|
||||
// stage1 对其他常量统一按 int_lit 打印(union 别名)。
|
||||
var iv: usize = 0;
|
||||
match c.lit {
|
||||
.Int(v) => { iv = v; },
|
||||
.Bool(v) => { if v { iv = 1; } },
|
||||
.Ptr(v) => { iv = v; },
|
||||
.Char(v) => { iv = v as usize; },
|
||||
.Fn(v) => { iv = v; },
|
||||
.Float(v) => {},
|
||||
}
|
||||
if !out_str(out, " int=") { ret false; }
|
||||
ret out_u(out, iv);
|
||||
},
|
||||
.TypeOp(t) => {
|
||||
if !dump_type(out, ty, t.tid) { ret false; }
|
||||
if @enum2int(e.kind) == @enum2int(IrKind.TypeOffsetof) {
|
||||
ret dump_ref(out, t.field_idx);
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
.AggConstruct(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < a.fields.len {
|
||||
if !dump_ref(out, list_at(a.fields, i)) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
.AggExtract(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
if !out_u(out, a.field_idx) { ret false; }
|
||||
ret dump_ref(out, a.val);
|
||||
},
|
||||
.AggInsert(a) => {
|
||||
if !dump_type(out, ty, a.tid) { ret false; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
if !out_u(out, a.field_idx) { ret false; }
|
||||
if !dump_ref(out, a.agg) { ret false; }
|
||||
ret dump_ref(out, a.field);
|
||||
},
|
||||
.Select(s) => {
|
||||
if !dump_type(out, ty, s.tid) { ret false; }
|
||||
if !dump_ref(out, s.cond) { ret false; }
|
||||
if !dump_ref(out, s.true_val) { ret false; }
|
||||
ret dump_ref(out, s.false_val);
|
||||
},
|
||||
.Br(b) => {
|
||||
if !dump_ref(out, b.cond) { ret false; }
|
||||
if !dump_ref(out, b.true_label) { ret false; }
|
||||
ret dump_ref(out, b.false_label);
|
||||
},
|
||||
.Jmp(j) => { ret dump_ref(out, j.label); },
|
||||
.Call(c) => {
|
||||
if !dump_type(out, ty, c.tid) { ret false; }
|
||||
if !dump_ref(out, c.func) { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < c.params.len {
|
||||
if !dump_ref(out, list_at(c.params, i)) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
.Param(p) => {
|
||||
if !dump_type(out, ty, p.tid) { ret false; }
|
||||
ret dump_ref(out, p.idx);
|
||||
},
|
||||
.Ret(r) => {
|
||||
if !dump_type(out, ty, r.tid) { ret false; }
|
||||
ret dump_ref(out, r.val);
|
||||
},
|
||||
.Panic(p) => {
|
||||
if !out_str(out, " \"") { ret false; }
|
||||
if !out_str(out, ByteView.as_slice(p.msg)) { ret false; }
|
||||
ret out_str(out, "\"");
|
||||
},
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump(self: *Ir, ty: *TypeTable, attr: *Attr, out: *ByteBuffer): bool {
|
||||
var i: usize = 1;
|
||||
while i < self.funcs.len {
|
||||
var f = func(self, i);
|
||||
if f == null { i = i + 1; continue; }
|
||||
if !out_str(out, "func ") { ret false; }
|
||||
if f.name.len == 0 {
|
||||
if !out_str(out, "?") { ret false; }
|
||||
} else {
|
||||
if !out_str(out, ByteView.as_slice(f.name)) { ret false; }
|
||||
}
|
||||
if !out_str(out, " %") { ret false; }
|
||||
if !out_u(out, i) { ret false; }
|
||||
if !out_str(out, " tid = ") { ret false; }
|
||||
if !TypeTable.pure_dump(ty, f.fn_tid, out) { ret false; }
|
||||
if !dump_attrs(out, attr, f.attrs) { ret false; }
|
||||
if !out_str(out, "\n") { ret false; }
|
||||
|
||||
var j: usize = 1;
|
||||
while j < f.nodes.len {
|
||||
var e = node(self, i, j);
|
||||
if e == null { j = j + 1; continue; }
|
||||
if !out_str(out, " #") { ret false; }
|
||||
if !out_u(out, j) { ret false; }
|
||||
if !out_str(out, " ") { ret false; }
|
||||
if !out_str(out, kind_name(e.kind)) { ret false; }
|
||||
if !dump_body(out, ty, e) { ret false; }
|
||||
if !dump_attrs(out, attr, e.attrs) { ret false; }
|
||||
if !out_str(out, "\n") { ret false; }
|
||||
j = j + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_kinds() {
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ArithAdd)), ByteView.from_slice("arith.add")),
|
||||
"arith.add name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ControlCall)),
|
||||
ByteView.from_slice("control.call")),
|
||||
"control.call name");
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(.DbgPanic)), ByteView.from_slice("dbg.panic")),
|
||||
"dbg.panic name");
|
||||
}
|
||||
|
||||
fn test_alloc_and_node() {
|
||||
var ir = new();
|
||||
defer { drop(&ir); }
|
||||
|
||||
var fid = alloc_fn(&ir);
|
||||
@assert(fid == 1, "first fn is 1");
|
||||
var nid = alloc_node(&ir, fid);
|
||||
@assert(nid == 1, "first node is 1");
|
||||
var e = node(&ir, fid, nid);
|
||||
@assert(e != null, "node lookup");
|
||||
e.kind = .ArithAdd;
|
||||
e.body = .Arith(.{ .tid = 2, .left = 1, .right = 1 });
|
||||
var f = func(&ir, fid);
|
||||
@assert(f != null && f.nodes.len == 2, "fn nodes");
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
test_kinds();
|
||||
test_alloc_and_node();
|
||||
}
|
||||
+1304
File diff suppressed because it is too large.
Load diff
+1378
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,497 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// SIR builder (IrBuilder). Split from ir.spl: stage1 ast2ir
|
||||
// infinitely recurses on same-file @self nesting; cross-file is fine.
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .Span, .span_none } = @import("./diag.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type Attr = @import("./attr.spl");
|
||||
type Ir = @import("./ir.spl");
|
||||
type .{ .IrKind, .ConstLit, .IrEntry, .IrFunc, .IrBody } = @import("./ir.spl");
|
||||
|
||||
fn list_at(v: Vec, i: usize): usize {
|
||||
if i >= v.len { ret 0; }
|
||||
ret ((v.data as *usize) + i).*;
|
||||
}
|
||||
|
||||
// ---- builder ----
|
||||
|
||||
type FixKind = enum { BrTrue, BrFalse, Jmp }
|
||||
|
||||
type Fix = struct {
|
||||
node: usize,
|
||||
slot: FixKind,
|
||||
block: usize,
|
||||
}
|
||||
|
||||
type IrBuilder = @self;
|
||||
|
||||
ir: Ir,
|
||||
dbg: Span,
|
||||
current_fn: usize,
|
||||
current_block: usize,
|
||||
blocks: Vec, // usize
|
||||
fixups: Vec, // Fix
|
||||
terminated: bool,
|
||||
|
||||
fn builder_alloc(self: *IrBuilder, kind: IrKind): usize {
|
||||
var ref = Ir.alloc_node(&self.ir, self.current_fn);
|
||||
if ref == 0 { ret 0; }
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.kind = kind;
|
||||
e.dbg = self.dbg;
|
||||
}
|
||||
if self.current_block >= self.blocks.len { ret ref; }
|
||||
match Vec.get(&self.blocks, self.current_block) {
|
||||
.Ok(p) => {
|
||||
var slot = p as *usize;
|
||||
if slot.* == 0 { slot.* = ref; }
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn builder_new(): IrBuilder {
|
||||
ret IrBuilder {
|
||||
.ir = Ir.new(),
|
||||
.dbg = span_none(),
|
||||
.current_fn = 0,
|
||||
.current_block = 0,
|
||||
.blocks = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.fixups = Vec.blank(@sizeof(Fix), @alignof(Fix)),
|
||||
.terminated = false,
|
||||
};
|
||||
}
|
||||
|
||||
fn builder_drop(self: *IrBuilder) {
|
||||
Ir.drop(&self.ir);
|
||||
Vec.drop(&self.blocks);
|
||||
Vec.drop(&self.fixups);
|
||||
}
|
||||
|
||||
fn fn_begin(self: *IrBuilder, name: ByteView, fn_tid: usize): usize {
|
||||
if self.current_fn != 0 { fn_end(self); }
|
||||
var fid = Ir.alloc_fn(&self.ir);
|
||||
var f = Ir.func(&self.ir, fid);
|
||||
if f != null {
|
||||
f.name = name;
|
||||
f.fn_tid = fn_tid;
|
||||
}
|
||||
self.current_fn = fid;
|
||||
self.current_block = 0;
|
||||
self.blocks.len = 0;
|
||||
self.fixups.len = 0;
|
||||
self.terminated = false;
|
||||
ret fid;
|
||||
}
|
||||
|
||||
fn fn_reserve(self: *IrBuilder, name: ByteView, fn_tid: usize): usize {
|
||||
var fid = Ir.alloc_fn(&self.ir);
|
||||
var f = Ir.func(&self.ir, fid);
|
||||
if f != null {
|
||||
f.name = name;
|
||||
f.fn_tid = fn_tid;
|
||||
}
|
||||
ret fid;
|
||||
}
|
||||
|
||||
fn fn_begin_at(self: *IrBuilder, fid: usize) {
|
||||
if self.current_fn != 0 && self.current_fn != fid { fn_end(self); }
|
||||
self.current_fn = fid;
|
||||
self.current_block = 0;
|
||||
self.blocks.len = 0;
|
||||
self.fixups.len = 0;
|
||||
self.terminated = false;
|
||||
}
|
||||
|
||||
fn fn_end(self: *IrBuilder) {
|
||||
var k: usize = 0;
|
||||
while k < self.fixups.len {
|
||||
match Vec.get(&self.fixups, k) {
|
||||
.Ok(fp) => {
|
||||
var fx = fp as *Fix;
|
||||
var e = Ir.node(&self.ir, self.current_fn, fx.node);
|
||||
if e != null {
|
||||
var target: usize = 0;
|
||||
if fx.block < self.blocks.len { target = list_at(self.blocks, fx.block); }
|
||||
if target != 0 {
|
||||
if @enum2int(fx.slot) == @enum2int(FixKind.BrTrue) {
|
||||
match e.body { .Br(b) => { b.true_label = target; e.body = .Br(b); }, _ => {} }
|
||||
} else if @enum2int(fx.slot) == @enum2int(FixKind.BrFalse) {
|
||||
match e.body { .Br(b) => { b.false_label = target; e.body = .Br(b); }, _ => {} }
|
||||
} else {
|
||||
match e.body { .Jmp(j) => { j.label = target; e.body = .Jmp(j); }, _ => {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.Err(error) => {},
|
||||
}
|
||||
k = k + 1;
|
||||
}
|
||||
self.current_block = 0;
|
||||
self.blocks.len = 0;
|
||||
self.fixups.len = 0;
|
||||
self.terminated = false;
|
||||
}
|
||||
|
||||
fn block_begin(self: *IrBuilder): usize {
|
||||
var zero: usize = 0;
|
||||
match Vec.push(&self.blocks, &zero as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var block = self.blocks.len - 1;
|
||||
self.current_block = block;
|
||||
self.terminated = false;
|
||||
ret block;
|
||||
}
|
||||
|
||||
fn block_end(self: *IrBuilder) {
|
||||
// terminated 与空块校验在 stage1 为 Panic;这里保持同样约束但不终止进程。
|
||||
}
|
||||
|
||||
// ---- arith / cmp / cast ----
|
||||
|
||||
fn set_arith(self: *IrBuilder, kind: IrKind, tid: usize, left: usize, right: usize): usize {
|
||||
var ref = builder_alloc(self, kind);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Arith(.{ .tid = tid, .left = left, .right = right }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn set_cmp(self: *IrBuilder, kind: IrKind, tid: usize, a: usize, b: usize): usize {
|
||||
var ref = builder_alloc(self, kind);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Cmp(.{ .tid = tid, .a = a, .b = b }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn set_cast(self: *IrBuilder, kind: IrKind, from_tid: usize, to_tid: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, kind);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Cast(.{ .from_tid = from_tid, .to_tid = to_tid, .val = val }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn arith_add(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithAdd, t, l, r); }
|
||||
fn arith_sub(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithSub, t, l, r); }
|
||||
fn arith_mul(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithMul, t, l, r); }
|
||||
fn arith_div(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithDiv, t, l, r); }
|
||||
fn arith_rem(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithRem, t, l, r); }
|
||||
fn arith_and(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithAnd, t, l, r); }
|
||||
fn arith_or(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithOr, t, l, r); }
|
||||
fn arith_xor(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithXor, t, l, r); }
|
||||
fn arith_shl(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithShl, t, l, r); }
|
||||
fn arith_shr(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithShr, t, l, r); }
|
||||
|
||||
fn arith_neg(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithNeg, t, v, 0); }
|
||||
fn arith_abs(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithAbs, t, v, 0); }
|
||||
fn arith_not(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithNot, t, v, 0); }
|
||||
|
||||
fn cmp_eq(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpEq, t, a, b); }
|
||||
fn cmp_ne(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpNe, t, a, b); }
|
||||
fn cmp_lt(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpLt, t, a, b); }
|
||||
fn cmp_le(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpLe, t, a, b); }
|
||||
fn cmp_gt(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpGt, t, a, b); }
|
||||
fn cmp_ge(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpGe, t, a, b); }
|
||||
|
||||
fn cast_trunc(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastTrunc, f, t, v); }
|
||||
fn cast_zext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastZext, f, t, v); }
|
||||
fn cast_sext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastSext, f, t, v); }
|
||||
fn cast_fext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastFext, f, t, v); }
|
||||
fn cast_ftrunc(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastFtrunc, f, t, v); }
|
||||
fn cast_bitcast(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastBitcast, f, t, v); }
|
||||
fn cast_ptr2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastPtr2int, f, t, v); }
|
||||
fn cast_int2ptr(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastInt2ptr, f, t, v); }
|
||||
fn cast_bool2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastBool2int, f, t, v); }
|
||||
fn case_int2float(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CaseInt2float, f, t, v); }
|
||||
fn case_float2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CaseFloat2int, f, t, v); }
|
||||
|
||||
// ---- memory ----
|
||||
|
||||
fn mem_alloca(self: *IrBuilder, tid: usize, count: usize): usize {
|
||||
var ref = builder_alloc(self, .MemAlloca);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemAlloc(.{ .tid = tid, .count = count }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_global_alloc(self: *IrBuilder, tid: usize, const_node: usize): usize {
|
||||
var ref = builder_alloc(self, .MemGlobalAlloc);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemGlobalAlloc(.{ .tid = tid, .const_node = const_node }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_load(self: *IrBuilder, tid: usize, ptr: usize): usize {
|
||||
var ref = builder_alloc(self, .MemLoad);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemLoad(.{ .tid = tid, .ptr = ptr }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_store(self: *IrBuilder, tid: usize, ptr: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, .MemStore);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemStore(.{ .tid = tid, .ptr = ptr, .val = val }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_offset(self: *IrBuilder, tid: usize, ptr: usize, offset: usize): usize {
|
||||
var ref = builder_alloc(self, .MemOffset);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemOffset(.{ .tid = tid, .ptr = ptr, .offset = offset }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_field(self: *IrBuilder, tid: usize, agg: usize, field_idx: usize): usize {
|
||||
var ref = builder_alloc(self, .MemFieldPtr);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemFieldPtr(.{ .tid = tid, .agg = agg, .field_idx = field_idx }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_copy(self: *IrBuilder, tid: usize, dst: usize, src: usize, size: usize): usize {
|
||||
var ref = builder_alloc(self, .MemCopy);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemCopy(.{ .tid = tid, .dst = dst, .src = src, .size = size }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn mem_set(self: *IrBuilder, tid: usize, dst: usize, val: usize, size: usize): usize {
|
||||
var ref = builder_alloc(self, .MemSet);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .MemSet(.{ .tid = tid, .dst = dst, .val = val, .size = size }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
// ---- type const ----
|
||||
|
||||
fn const_node(self: *IrBuilder, tid: usize, lit: ConstLit): usize {
|
||||
var ref = builder_alloc(self, .TypeConst);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Const(.{ .tid = tid, .lit = lit }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_const_ptr(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Ptr(val)); }
|
||||
fn type_const_bool(self: *IrBuilder, tid: usize, val: bool): usize { ret const_node(self, tid, .Bool(val)); }
|
||||
fn type_const_int(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Int(val)); }
|
||||
fn type_const_float(self: *IrBuilder, tid: usize, val: f64): usize { ret const_node(self, tid, .Float(val)); }
|
||||
fn type_const_char(self: *IrBuilder, tid: usize, val: u8): usize { ret const_node(self, tid, .Char(val)); }
|
||||
fn type_const_fn(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Fn(val)); }
|
||||
fn type_const_bytes(self: *IrBuilder, tid: usize, data: usize): usize { ret const_node(self, tid, .Ptr(data)); }
|
||||
|
||||
// ---- type ops ----
|
||||
|
||||
fn type_bitsizeof(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeBitsizeof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_sizeof(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeSizeof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_alignof(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeAlignof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_offsetof(self: *IrBuilder, tid: usize, field_idx: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeOffsetof);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = field_idx }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn type_field_count(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .TypeFieldCount);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
// ---- agg ----
|
||||
|
||||
fn agg_construct(self: *IrBuilder, tid: usize): usize {
|
||||
var ref = builder_alloc(self, .AggConstruct);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.body = .AggConstruct(.{ .tid = tid, .fields = Vec.blank(@sizeof(usize), @alignof(usize)) });
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn agg_construct_field(self: *IrBuilder, node_ref: usize, field: usize) {
|
||||
var e = Ir.node(&self.ir, self.current_fn, node_ref);
|
||||
if e == null { ret; }
|
||||
// 载荷按值绑定:改副本后写回。
|
||||
match e.body {
|
||||
.AggConstruct(a) => {
|
||||
match Vec.push(&a.fields, &field as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
e.body = .AggConstruct(a);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn agg_extract(self: *IrBuilder, tid: usize, field_tid: usize, field_idx: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, .AggExtract);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.body = .AggExtract(.{ .tid = tid, .field_tid = field_tid, .field_idx = field_idx, .val = val });
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn agg_insert(self: *IrBuilder, tid: usize, field_idx: usize, agg: usize, field: usize): usize {
|
||||
var ref = builder_alloc(self, .AggInsert);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .AggInsert(.{ .tid = tid, .field_idx = field_idx, .agg = agg, .field = field }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
// ---- control ----
|
||||
|
||||
fn control_select(self: *IrBuilder, tid: usize, cond: usize, tv: usize, fv: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlSelect);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Select(.{ .tid = tid, .cond = cond, .true_val = tv, .false_val = fv }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_br(self: *IrBuilder, cond: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlBr);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Br(.{ .cond = cond, .true_label = 0, .false_label = 0 }); }
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_br_fixup(self: *IrBuilder, ref: usize, true_block: usize, false_block: usize) {
|
||||
var f1 = Fix { .node = ref, .slot = .BrTrue, .block = true_block };
|
||||
match Vec.push(&self.fixups, &f1 as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var f2 = Fix { .node = ref, .slot = .BrFalse, .block = false_block };
|
||||
match Vec.push(&self.fixups, &f2 as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
|
||||
fn control_jmp(self: *IrBuilder): usize {
|
||||
var ref = builder_alloc(self, .ControlJmp);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Jmp(.{ .label = 0 }); }
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_jmp_fixup(self: *IrBuilder, ref: usize, target_block: usize) {
|
||||
var fx = Fix { .node = ref, .slot = .Jmp, .block = target_block };
|
||||
match Vec.push(&self.fixups, &fx as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
|
||||
fn control_call(self: *IrBuilder, tid: usize, fn_ref: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlCall);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null {
|
||||
e.body = .Call(.{ .tid = tid, .func = fn_ref,
|
||||
.params = Vec.blank(@sizeof(usize), @alignof(usize)) });
|
||||
}
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_call_param(self: *IrBuilder, call_node: usize, arg: usize) {
|
||||
var e = Ir.node(&self.ir, self.current_fn, call_node);
|
||||
if e == null { ret; }
|
||||
// 载荷按值绑定:改副本后写回。
|
||||
match e.body {
|
||||
.Call(c) => {
|
||||
match Vec.push(&c.params, &arg as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
e.body = .Call(c);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn control_param(self: *IrBuilder, tid: usize, idx: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlParam);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Param(.{ .tid = tid, .idx = idx }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_ret(self: *IrBuilder, tid: usize, val: usize): usize {
|
||||
var ref = builder_alloc(self, .ControlRet);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Ret(.{ .tid = tid, .val = val }); }
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_unreachable(self: *IrBuilder): usize {
|
||||
var ref = builder_alloc(self, .ControlUnreachable);
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn control_trap(self: *IrBuilder): usize {
|
||||
var ref = builder_alloc(self, .ControlTrap);
|
||||
self.terminated = true;
|
||||
ret ref;
|
||||
}
|
||||
|
||||
fn dbg_breakpoint(self: *IrBuilder): usize {
|
||||
ret builder_alloc(self, .DbgBreakpoint);
|
||||
}
|
||||
|
||||
fn dbg_panic(self: *IrBuilder, msg: ByteView): usize {
|
||||
var ref = builder_alloc(self, .DbgPanic);
|
||||
var e = Ir.node(&self.ir, self.current_fn, ref);
|
||||
if e != null { e.body = .Panic(.{ .msg = msg }); }
|
||||
ret ref;
|
||||
}
|
||||
|
||||
|
||||
fn test_builder_emit_and_dump() {
|
||||
var b = builder_new();
|
||||
defer { builder_drop(&b); }
|
||||
|
||||
b.dbg = span_none();
|
||||
var fid = fn_begin(&b, ByteView.from_slice("f"), 3);
|
||||
var blk = block_begin(&b);
|
||||
var p = control_param(&b, 2, 0);
|
||||
var c = type_const_int(&b, 2, 42);
|
||||
var add = arith_add(&b, 2, p, c);
|
||||
control_ret(&b, 2, add);
|
||||
block_end(&b);
|
||||
fn_end(&b);
|
||||
|
||||
var ty = TypeTable.new();
|
||||
var t: TypeTable = undefined;
|
||||
match ty { .Ok(v) => { t = v; }, .Err(e) => { @assert(false, "tt"); } }
|
||||
defer { TypeTable.drop(&t); }
|
||||
var at = Attr.new();
|
||||
var a: Attr = undefined;
|
||||
match at { .Ok(v) => { a = v; }, .Err(e) => { @assert(false, "attr"); } }
|
||||
defer { Attr.drop(&a); }
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
@assert(Ir.dump(&b.ir, &t, &a, &out), "dump ok");
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
@assert(got.len > 0, "dump nonempty");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_builder_emit_and_dump();
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,354 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 目标布局(移植自 stage1/spl_layout.c)。输入是 IR 类型表 TypeTable;
|
||||
// 为每个类型计算 size_bits / align_bits / 字段位偏移,供 ir2vm 使用。
|
||||
// 默认 64 位平台:标量按 1/2/4/8 字节对齐,指针 = usize,聚合按最大字段对齐
|
||||
// 且不允许重排(与 stage1 的默认策略一致)。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type TypeTable = @import("./type_table.spl");
|
||||
type .{ .TypeNode, .Agg, .AggKind, .Member } = @import("./type_table.spl");
|
||||
type .{ .node, .member_at, .make_int, .make_array, .make_agg, .push_member } = @import("./type_table.spl");
|
||||
|
||||
type LayoutInfo = struct {
|
||||
size_bits: usize,
|
||||
align_bits: usize,
|
||||
field_bit_offsets: Vec, // usize
|
||||
field_bit_sizes: Vec, // usize
|
||||
resolved: bool,
|
||||
}
|
||||
|
||||
type Layout = @self;
|
||||
|
||||
infos: Vec, // LayoutInfo
|
||||
ptr_size: usize,
|
||||
ptr_align: usize,
|
||||
|
||||
type LayoutResult = enum {
|
||||
Ok: Layout,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn blank_usizes(): Vec {
|
||||
ret Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
}
|
||||
|
||||
fn new(): LayoutResult {
|
||||
ret .Ok(Layout {
|
||||
.infos = Vec.blank(@sizeof(LayoutInfo), @alignof(LayoutInfo)),
|
||||
.ptr_size = @sizeof(usize),
|
||||
.ptr_align = @alignof(usize),
|
||||
});
|
||||
}
|
||||
|
||||
fn info_at(self: *Layout, id: usize): *LayoutInfo {
|
||||
if id == 0 || id >= self.infos.len { ret null; }
|
||||
ret (self.infos.data as *LayoutInfo) + id;
|
||||
}
|
||||
|
||||
// 未解析或越界返回 null(对应 stage1 spl_layout_info_of)。
|
||||
fn info_of(self: *Layout, id: usize): *LayoutInfo {
|
||||
var info = info_at(self, id);
|
||||
if info == null || !info.resolved { ret null; }
|
||||
ret info;
|
||||
}
|
||||
|
||||
fn drop(self: *Layout) {
|
||||
var i: usize = 0;
|
||||
while i < self.infos.len {
|
||||
var info = info_at(self, i);
|
||||
if info != null {
|
||||
Vec.drop(&info.field_bit_offsets);
|
||||
Vec.drop(&info.field_bit_sizes);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.infos);
|
||||
}
|
||||
|
||||
fn ensure(self: *Layout, n: usize) {
|
||||
while self.infos.len < n {
|
||||
var info = LayoutInfo {
|
||||
.size_bits = 0,
|
||||
.align_bits = 0,
|
||||
.field_bit_offsets = blank_usizes(),
|
||||
.field_bit_sizes = blank_usizes(),
|
||||
.resolved = false,
|
||||
};
|
||||
match Vec.push(&self.infos, &info as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret; },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_usize(v: *Vec, x: usize) {
|
||||
match Vec.push(v, &x as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn usize_at(v: *Vec, i: usize): usize {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 标量对齐(字节);0 表示没有匹配的标量(即未落地)。
|
||||
fn scalar_align(size_bytes: usize): usize {
|
||||
if size_bytes == 1 { ret 1; }
|
||||
if size_bytes == 2 { ret 2; }
|
||||
if size_bytes == 4 { ret 4; }
|
||||
if size_bytes == 8 { ret 8; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn round_up_bits(x: usize, align_bits: usize): usize {
|
||||
if align_bits <= 1 { ret x; }
|
||||
ret (x + align_bits - 1) / align_bits * align_bits;
|
||||
}
|
||||
|
||||
fn layout_scalar(info: *LayoutInfo, size_bits: usize) {
|
||||
var size_bytes = (size_bits + 7) / 8;
|
||||
var a = scalar_align(size_bytes);
|
||||
if a == 0 { ret; }
|
||||
info.size_bits = size_bytes * 8;
|
||||
info.align_bits = a * 8;
|
||||
}
|
||||
|
||||
fn layout_ptr(self: *Layout, info: *LayoutInfo) {
|
||||
if self.ptr_size == 0 { ret; }
|
||||
info.size_bits = self.ptr_size * 8;
|
||||
info.align_bits = self.ptr_align * 8;
|
||||
}
|
||||
|
||||
// enum 与 struct/union/shape 的布局。字段索引约定(与 ir2vm 一致):
|
||||
// enum: 0 = tag,1..n = 各变体载荷(同一偏移);其余按声明顺序。
|
||||
fn layout_agg(self: *Layout, is_enum: bool, is_union: bool, agg: Agg, info: *LayoutInfo) {
|
||||
var n = agg.variants.len;
|
||||
|
||||
if is_enum {
|
||||
if agg.tag_type == 0 { ret; }
|
||||
var tag_info = info_at(self, agg.tag_type);
|
||||
if tag_info == null || !tag_info.resolved { ret; }
|
||||
|
||||
var tag_align_bits = tag_info.align_bits;
|
||||
if tag_align_bits == 0 { tag_align_bits = 8; }
|
||||
var payload_align_bits: usize = 8;
|
||||
var payload_size_bits: usize = 0;
|
||||
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null && m.type_id != 0 {
|
||||
var fi = info_at(self, m.type_id);
|
||||
if fi == null || !fi.resolved { ret; }
|
||||
var fa = fi.align_bits;
|
||||
if fa == 0 { fa = 8; }
|
||||
if fa > payload_align_bits { payload_align_bits = fa; }
|
||||
if fi.size_bits > payload_size_bits { payload_size_bits = fi.size_bits; }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var payload_off = round_up_bits(tag_info.size_bits, payload_align_bits);
|
||||
var final_align = tag_align_bits;
|
||||
if payload_align_bits > final_align { final_align = payload_align_bits; }
|
||||
info.size_bits = round_up_bits(payload_off + payload_size_bits, final_align);
|
||||
info.align_bits = final_align;
|
||||
|
||||
push_usize(&info.field_bit_offsets, 0);
|
||||
push_usize(&info.field_bit_sizes, tag_info.size_bits);
|
||||
i = 0;
|
||||
while i < n {
|
||||
var m = member_at(&agg.variants, i);
|
||||
var fsize: usize = 0;
|
||||
if m != null && m.type_id != 0 {
|
||||
var fi = info_at(self, m.type_id);
|
||||
if fi != null { fsize = fi.size_bits; }
|
||||
}
|
||||
push_usize(&info.field_bit_offsets, payload_off);
|
||||
push_usize(&info.field_bit_sizes, fsize);
|
||||
i = i + 1;
|
||||
}
|
||||
ret;
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
info.size_bits = 0;
|
||||
info.align_bits = 8;
|
||||
ret;
|
||||
}
|
||||
|
||||
var max_align_bits: usize = 8;
|
||||
var offset_bits: usize = 0;
|
||||
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
var m = member_at(&agg.variants, i);
|
||||
var fsize_bits: usize = 0;
|
||||
var fsize_align: usize = 8;
|
||||
if m != null && m.type_id != 0 {
|
||||
var fi = info_at(self, m.type_id);
|
||||
if fi == null || !fi.resolved { ret; }
|
||||
fsize_bits = fi.size_bits;
|
||||
fsize_align = fi.align_bits;
|
||||
if fsize_align == 0 { fsize_align = 8; }
|
||||
}
|
||||
|
||||
if is_union {
|
||||
push_usize(&info.field_bit_offsets, 0);
|
||||
push_usize(&info.field_bit_sizes, fsize_bits);
|
||||
if fsize_bits > info.size_bits { info.size_bits = fsize_bits; }
|
||||
} else {
|
||||
offset_bits = round_up_bits(offset_bits, fsize_align);
|
||||
push_usize(&info.field_bit_offsets, offset_bits);
|
||||
push_usize(&info.field_bit_sizes, fsize_bits);
|
||||
offset_bits = offset_bits + fsize_bits;
|
||||
}
|
||||
if fsize_align > max_align_bits { max_align_bits = fsize_align; }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if is_union {
|
||||
info.size_bits = round_up_bits(info.size_bits, max_align_bits);
|
||||
} else {
|
||||
info.size_bits = round_up_bits(offset_bits, max_align_bits);
|
||||
}
|
||||
info.align_bits = max_align_bits;
|
||||
}
|
||||
|
||||
fn resolve_members(self: *Layout, tt: *TypeTable, agg: Agg) {
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null && m.type_id != 0 { resolve_one(self, tt, m.type_id); }
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_one(self: *Layout, tt: *TypeTable, id: usize) {
|
||||
var info = info_at(self, id);
|
||||
if info == null || info.resolved { ret; }
|
||||
var e = node(tt, id);
|
||||
if e == null { ret; }
|
||||
|
||||
info.resolved = true;
|
||||
|
||||
match e.node {
|
||||
.Error => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Infer => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Forall => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Exists => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Never => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Unit => { info.size_bits = 0; info.align_bits = 8; },
|
||||
.Any => { layout_ptr(self, info); },
|
||||
.Bool => { layout_scalar(info, 8); },
|
||||
.Int(x) => { layout_scalar(info, x.bits); },
|
||||
.Float(x) => { layout_scalar(info, x.bits); },
|
||||
.Ptr(x) => { layout_ptr(self, info); },
|
||||
.Array(a) => {
|
||||
resolve_one(self, tt, a.element);
|
||||
var elem = info_at(self, a.element);
|
||||
if elem == null || !elem.resolved { ret; }
|
||||
info.size_bits = elem.size_bits * a.len;
|
||||
info.align_bits = elem.align_bits;
|
||||
},
|
||||
.Struct(agg) => {
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, false, false, agg, info);
|
||||
},
|
||||
.Union(agg) => {
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, false, true, agg, info);
|
||||
},
|
||||
.Enum(agg) => {
|
||||
if agg.tag_type != 0 { resolve_one(self, tt, agg.tag_type); }
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, true, false, agg, info);
|
||||
},
|
||||
.Shape(agg) => {
|
||||
resolve_members(self, tt, agg);
|
||||
layout_agg(self, false, false, agg, info);
|
||||
},
|
||||
.Fn(f) => {
|
||||
var i: usize = 0;
|
||||
while i < f.params.len {
|
||||
resolve_one(self, tt, usize_at(&f.params, i));
|
||||
i = i + 1;
|
||||
}
|
||||
resolve_one(self, tt, f.ret_type);
|
||||
layout_ptr(self, info);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(self: *Layout, tt: *TypeTable) {
|
||||
ensure(self, tt.table.len);
|
||||
var i: usize = 1;
|
||||
while i < tt.table.len {
|
||||
resolve_one(self, tt, i);
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var tt: TypeTable = undefined;
|
||||
match TypeTable.new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "layout test: tt init"); ret; },
|
||||
}
|
||||
defer { TypeTable.drop(&tt); }
|
||||
|
||||
var i32t = make_int(&tt, 32, true);
|
||||
var u8t = make_int(&tt, 8, false);
|
||||
|
||||
var members = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&members, "a", i32t);
|
||||
push_member(&members, "b", u8t);
|
||||
var agg = Agg { .mode = .Auto, .fixed_align_bits = 0, .tag_type = 0, .variants = members };
|
||||
var sid = make_agg(&tt, .Struct, agg);
|
||||
|
||||
var layout: Layout = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { layout = v; },
|
||||
.Err(error) => { @assert(false, "layout test: layout init"); ret; },
|
||||
}
|
||||
defer { drop(&layout); }
|
||||
|
||||
resolve(&layout, &tt);
|
||||
var info = info_of(&layout, sid);
|
||||
if info == null {
|
||||
@assert(false, "struct layout missing");
|
||||
ret;
|
||||
}
|
||||
@assert(info.size_bits == 64, "struct size_bits");
|
||||
@assert(info.align_bits == 32, "struct align_bits");
|
||||
@assert(usize_at(&info.field_bit_offsets, 0) == 0, "field0 off");
|
||||
@assert(usize_at(&info.field_bit_offsets, 1) == 32, "field1 off");
|
||||
|
||||
var arr = make_array(&tt, i32t, 3);
|
||||
resolve(&layout, &tt);
|
||||
var ainfo = info_of(&layout, arr);
|
||||
if ainfo == null {
|
||||
@assert(false, "array layout missing");
|
||||
ret;
|
||||
}
|
||||
@assert(ainfo.size_bits == 96, "array size_bits");
|
||||
@assert(ainfo.align_bits == 32, "array align_bits");
|
||||
|
||||
var pt = TypeTable.make_ptr(&tt, u8t);
|
||||
resolve(&layout, &tt);
|
||||
var pinfo = info_of(&layout, pt);
|
||||
if pinfo == null {
|
||||
@assert(false, "ptr layout missing");
|
||||
ret;
|
||||
}
|
||||
@assert(pinfo.size_bits == @sizeof(usize) * 8, "ptr size_bits");
|
||||
|
||||
ret;
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 词法分析器:字节流 -> Vec<Token>。镜像 stage1/spl_lexer.c 的行为:
|
||||
// 行注释作为 token 产出;块注释(可嵌套)整体跳过;换行产出 Endline;EOF 产出 Eof。
|
||||
// 复用 utils/ascii 做字符分类;整型/字符在词法期解码后放进 TokenKind 载荷。
|
||||
// Token.text 借用输入字节,不复制。
|
||||
|
||||
type .{ .Vec, .VecResult } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .is_digit, .is_alpha, .is_alnum, .is_hex_digit, .digit_value } = @import("utils/ascii.spl");
|
||||
type .{
|
||||
.Token, .TokenKind, .Radix, .Keyword, .Punct, .keyword_of
|
||||
} = @import("token.spl");
|
||||
type .{ .Span, .span_make } = @import("./diag.spl");
|
||||
type Diag = @import("./diag.spl");
|
||||
|
||||
type Lexer = @self;
|
||||
|
||||
src: ByteView,
|
||||
pos: usize,
|
||||
line: usize,
|
||||
col: usize,
|
||||
diag: *Diag, // 借用;可为 null(合法输入不产生诊断)
|
||||
file: usize,
|
||||
|
||||
fn new(src: ByteView, diag: *Diag, file: usize): Lexer {
|
||||
ret Lexer { .src = src, .pos = 0, .line = 1, .col = 1, .diag = diag, .file = file };
|
||||
}
|
||||
|
||||
// 记录一条词法诊断(非法输入路径)。合法输入不应触发。
|
||||
fn report(self: *Lexer, start: usize, end: usize, message: []u8) {
|
||||
if self.diag == null { ret; }
|
||||
Diag.err(self.diag, span_make(self.file, start, end), .LexUnexpected, message);
|
||||
}
|
||||
|
||||
fn is_ident_start(c: u8): bool { ret is_alpha(c) || c == '_'; }
|
||||
|
||||
fn is_ident_cont(c: u8): bool { ret is_alnum(c) || c == '_'; }
|
||||
|
||||
fn hex_val(c: u8): u8 {
|
||||
match digit_value(c, 16) {
|
||||
.Ok(v) => { ret v; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 文本形如 'a' / '\n' / '\x41';返回解码后的字节。
|
||||
fn decode_char(text: ByteView): u8 {
|
||||
var start: usize = 1;
|
||||
var end = text.len;
|
||||
if text.len >= 2 && text.ptr[text.len - 1] == '\'' { end = text.len - 1; }
|
||||
if end <= start { ret 0; }
|
||||
|
||||
var b = text.ptr[start];
|
||||
if b != '\\' { ret b; }
|
||||
|
||||
start = start + 1;
|
||||
if start >= end { ret 0; }
|
||||
var e = text.ptr[start];
|
||||
if e == 'n' { ret '\n'; }
|
||||
if e == 't' { ret '\t'; }
|
||||
if e == 'r' { ret '\r'; }
|
||||
if e == '\\' { ret '\\'; }
|
||||
if e == '"' { ret '"'; }
|
||||
if e == '\'' { ret '\''; }
|
||||
if e == '0' { ret 0; }
|
||||
if e == 'x' || e == 'X' {
|
||||
var q = start + 1;
|
||||
var value: u8 = 0;
|
||||
var digits: usize = 0;
|
||||
while q < end && digits < 2 && is_hex_digit(text.ptr[q]) {
|
||||
value = value * 16 + hex_val(text.ptr[q]);
|
||||
q = q + 1;
|
||||
digits = digits + 1;
|
||||
}
|
||||
if digits == 0 { ret e; }
|
||||
ret value;
|
||||
}
|
||||
ret e;
|
||||
}
|
||||
|
||||
// ---- 读取辅助 ----
|
||||
|
||||
fn has2(self: *Lexer, ch: u8): bool {
|
||||
ret self.pos + 1 < self.src.len && self.src.ptr[self.pos + 1] == ch;
|
||||
}
|
||||
|
||||
fn has3(self: *Lexer, ch1: u8, ch2: u8): bool {
|
||||
ret self.pos + 2 < self.src.len && self.src.ptr[self.pos + 1] == ch1 &&
|
||||
self.src.ptr[self.pos + 2] == ch2;
|
||||
}
|
||||
|
||||
fn sub(self: *Lexer, start: usize, end: usize): ByteView {
|
||||
if start >= end { ret ByteView.empty(); }
|
||||
ret ByteView { .ptr = self.src.ptr + start, .len = end - start };
|
||||
}
|
||||
|
||||
// 产出 token 并把 pos/col 推进到 end。Vec.push 失败视为致命(同 vec.make_vec 风格)。
|
||||
fn emit_to(self: *Lexer, out: *Vec, kind: TokenKind, start: usize, end: usize, line: usize,
|
||||
col: usize) {
|
||||
var t = Token.make(kind, sub(self, start, end), line, col, start);
|
||||
match Vec.push(out, &t as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "lexer: Vec.push failed"); },
|
||||
}
|
||||
self.pos = end;
|
||||
self.col = col + (end - start);
|
||||
}
|
||||
|
||||
fn op1(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) {
|
||||
emit_to(self, out, .Punct(punct), start, start + 1, line, col);
|
||||
}
|
||||
|
||||
fn op2(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) {
|
||||
emit_to(self, out, .Punct(punct), start, start + 2, line, col);
|
||||
}
|
||||
|
||||
fn op3(self: *Lexer, out: *Vec, punct: Punct, start: usize, line: usize, col: usize) {
|
||||
emit_to(self, out, .Punct(punct), start, start + 3, line, col);
|
||||
}
|
||||
|
||||
// ---- 复合扫描 ----
|
||||
|
||||
fn scan_int(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
||||
var p = self.pos;
|
||||
if self.src.ptr[p] == '0' && p + 1 < self.src.len {
|
||||
var nc = self.src.ptr[p + 1];
|
||||
if nc == 'x' || nc == 'X' {
|
||||
p = p + 2;
|
||||
while p < self.src.len && (is_hex_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') {
|
||||
p = p + 1;
|
||||
}
|
||||
emit_to(self, out, .Int(.Hex), start, p, line, col);
|
||||
ret;
|
||||
}
|
||||
if nc == 'b' || nc == 'B' {
|
||||
p = p + 2;
|
||||
while p < self.src.len && (self.src.ptr[p] == '0' || self.src.ptr[p] == '1' ||
|
||||
self.src.ptr[p] == '_') {
|
||||
p = p + 1;
|
||||
}
|
||||
emit_to(self, out, .Int(.Bin), start, p, line, col);
|
||||
ret;
|
||||
}
|
||||
if nc == 'o' || nc == 'O' {
|
||||
p = p + 2;
|
||||
while p < self.src.len &&
|
||||
((self.src.ptr[p] >= '0' && self.src.ptr[p] <= '7') ||
|
||||
self.src.ptr[p] == '_') {
|
||||
p = p + 1;
|
||||
}
|
||||
emit_to(self, out, .Int(.Oct), start, p, line, col);
|
||||
ret;
|
||||
}
|
||||
}
|
||||
|
||||
while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') { p = p + 1; }
|
||||
if p + 1 < self.src.len && self.src.ptr[p] == '.' && is_digit(self.src.ptr[p + 1]) {
|
||||
p = p + 1;
|
||||
// 小数部分允许下划线分隔(Rust 风格)。
|
||||
while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') {
|
||||
p = p + 1;
|
||||
}
|
||||
// 可选指数:e/E [+-] 数字(数字允许下划线);仅当确有数字时纳入。
|
||||
if p < self.src.len && (self.src.ptr[p] == 'e' || self.src.ptr[p] == 'E') {
|
||||
var q = p + 1;
|
||||
if q < self.src.len && (self.src.ptr[q] == '+' || self.src.ptr[q] == '-') { q = q + 1; }
|
||||
if q < self.src.len && is_digit(self.src.ptr[q]) {
|
||||
p = q + 1;
|
||||
while p < self.src.len && (is_digit(self.src.ptr[p]) || self.src.ptr[p] == '_') {
|
||||
p = p + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
emit_to(self, out, .Float, start, p, line, col);
|
||||
ret;
|
||||
}
|
||||
emit_to(self, out, .Int(.Dec), start, p, line, col);
|
||||
}
|
||||
|
||||
fn scan_char(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
||||
var p = self.pos + 1;
|
||||
var closed = false;
|
||||
var escape_ok = true;
|
||||
if p < self.src.len {
|
||||
if self.src.ptr[p] == '\\' {
|
||||
p = p + 1;
|
||||
if p < self.src.len && (self.src.ptr[p] == 'x' || self.src.ptr[p] == 'X') {
|
||||
p = p + 1;
|
||||
var digits: usize = 0;
|
||||
while p < self.src.len && digits < 2 && is_hex_digit(self.src.ptr[p]) {
|
||||
p = p + 1;
|
||||
digits = digits + 1;
|
||||
}
|
||||
if digits == 0 { escape_ok = false; }
|
||||
} else if p < self.src.len {
|
||||
var e = self.src.ptr[p];
|
||||
if !(e == 'n' || e == 't' || e == 'r' || e == '\\' || e == '"' || e == '\'' ||
|
||||
e == '0') {
|
||||
escape_ok = false;
|
||||
}
|
||||
p = p + 1;
|
||||
} else {
|
||||
escape_ok = false;
|
||||
}
|
||||
} else {
|
||||
p = p + 1;
|
||||
}
|
||||
}
|
||||
if p < self.src.len && self.src.ptr[p] == '\'' {
|
||||
p = p + 1;
|
||||
closed = true;
|
||||
}
|
||||
if !closed {
|
||||
report(self, start, p, "unterminated character literal");
|
||||
} else if !escape_ok {
|
||||
report(self, start, p, "invalid escape in character literal");
|
||||
}
|
||||
emit_to(self, out, .Char(decode_char(sub(self, start, p))), start, p, line, col);
|
||||
}
|
||||
|
||||
fn scan_string(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
||||
var p = self.pos + 1;
|
||||
var l = line;
|
||||
var cc = col + 1;
|
||||
var closed = false;
|
||||
while p < self.src.len {
|
||||
var b = self.src.ptr[p];
|
||||
if b == '\\' {
|
||||
p = p + 1;
|
||||
cc = cc + 1;
|
||||
if p < self.src.len {
|
||||
p = p + 1;
|
||||
cc = cc + 1;
|
||||
}
|
||||
} else if b == '"' {
|
||||
p = p + 1;
|
||||
cc = cc + 1;
|
||||
closed = true;
|
||||
break;
|
||||
} else if b == '\n' {
|
||||
l = l + 1;
|
||||
cc = 1;
|
||||
p = p + 1;
|
||||
} else {
|
||||
p = p + 1;
|
||||
cc = cc + 1;
|
||||
}
|
||||
}
|
||||
if !closed { report(self, start, p, "unterminated string literal"); }
|
||||
emit_to(self, out, .Str, start, p, line, col);
|
||||
self.line = l;
|
||||
self.col = cc;
|
||||
}
|
||||
|
||||
fn scan_ident(self: *Lexer, out: *Vec, start: usize, line: usize, col: usize) {
|
||||
var p = self.pos;
|
||||
while p < self.src.len && is_ident_cont(self.src.ptr[p]) { p = p + 1; }
|
||||
var text = sub(self, start, p);
|
||||
match keyword_of(text) {
|
||||
.Yes(kw) => { emit_to(self, out, .Keyword(kw), start, p, line, col); },
|
||||
.No => { emit_to(self, out, .Ident, start, p, line, col); },
|
||||
}
|
||||
}
|
||||
|
||||
// 块注释:可嵌套,整体跳过(不产出 token)。未闭合时报诊断。
|
||||
fn skip_block_comment(self: *Lexer, start: usize) {
|
||||
self.pos = self.pos + 2;
|
||||
self.col = self.col + 2;
|
||||
var depth: usize = 1;
|
||||
while self.pos + 1 < self.src.len && depth > 0 {
|
||||
if self.src.ptr[self.pos] == '*' && self.src.ptr[self.pos + 1] == '/' {
|
||||
depth = depth - 1;
|
||||
self.pos = self.pos + 2;
|
||||
self.col = self.col + 2;
|
||||
} else if self.src.ptr[self.pos] == '/' && self.src.ptr[self.pos + 1] == '*' {
|
||||
depth = depth + 1;
|
||||
self.pos = self.pos + 2;
|
||||
self.col = self.col + 2;
|
||||
} else {
|
||||
if self.src.ptr[self.pos] == '\n' {
|
||||
self.line = self.line + 1;
|
||||
self.col = 1;
|
||||
} else {
|
||||
self.col = self.col + 1;
|
||||
}
|
||||
self.pos = self.pos + 1;
|
||||
}
|
||||
}
|
||||
if depth > 0 {
|
||||
report(self, start, self.src.len, "unterminated block comment");
|
||||
}
|
||||
}
|
||||
|
||||
fn run(self: *Lexer, out: *Vec) {
|
||||
while self.pos < self.src.len {
|
||||
var start = self.pos;
|
||||
var line = self.line;
|
||||
var col = self.col;
|
||||
var c = self.src.ptr[self.pos];
|
||||
|
||||
if c == ' ' || c == '\t' || c == '\r' {
|
||||
self.pos = self.pos + 1;
|
||||
self.col = self.col + 1;
|
||||
continue;
|
||||
}
|
||||
if c == '\n' {
|
||||
emit_to(self, out, .Endline, start, start + 1, line, col);
|
||||
self.line = line + 1;
|
||||
self.col = 1;
|
||||
continue;
|
||||
}
|
||||
if c == '/' && has2(self, '/') {
|
||||
var p = self.pos;
|
||||
while p < self.src.len && self.src.ptr[p] != '\n' { p = p + 1; }
|
||||
emit_to(self, out, .LineComment, start, p, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '/' && has2(self, '*') {
|
||||
skip_block_comment(self, start);
|
||||
continue;
|
||||
}
|
||||
if c == '\'' {
|
||||
scan_char(self, out, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '"' {
|
||||
scan_string(self, out, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if is_ident_start(c) {
|
||||
scan_ident(self, out, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if is_digit(c) {
|
||||
scan_int(self, out, start, line, col);
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '<' {
|
||||
if has3(self, '-', '>') { op3(self, out, .ArrowBoth, start, line, col); continue; }
|
||||
if has3(self, '=', '>') { op3(self, out, .FatArrowBoth, start, line, col); continue; }
|
||||
if has3(self, '<', '=') { op3(self, out, .LShAssign, start, line, col); continue; }
|
||||
if has2(self, '<') { op2(self, out, .LSh, start, line, col); continue; }
|
||||
if has2(self, '-') { op2(self, out, .LeftArrow, start, line, col); continue; }
|
||||
if has2(self, '=') { op2(self, out, .Le, start, line, col); continue; }
|
||||
op1(self, out, .Lt, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '>' {
|
||||
if has3(self, '>', '=') { op3(self, out, .RShAssign, start, line, col); continue; }
|
||||
if has2(self, '>') { op2(self, out, .RSh, start, line, col); continue; }
|
||||
if has2(self, '=') { op2(self, out, .Ge, start, line, col); continue; }
|
||||
op1(self, out, .Gt, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '.' {
|
||||
if has3(self, '.', '.') { op3(self, out, .Ellipsis, start, line, col); continue; }
|
||||
if has2(self, '.') { op2(self, out, .Range, start, line, col); continue; }
|
||||
op1(self, out, .Dot, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '=' {
|
||||
if has2(self, '=') { op2(self, out, .Eq, start, line, col); continue; }
|
||||
if has2(self, '>') { op2(self, out, .RightFatArrow, start, line, col); continue; }
|
||||
op1(self, out, .Assign, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '!' {
|
||||
if has2(self, '=') { op2(self, out, .Neq, start, line, col); continue; }
|
||||
op1(self, out, .Not, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '&' {
|
||||
if has2(self, '&') { op2(self, out, .AndAnd, start, line, col); continue; }
|
||||
if has2(self, '=') { op2(self, out, .AndAssign, start, line, col); continue; }
|
||||
op1(self, out, .And, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '|' {
|
||||
if has2(self, '|') { op2(self, out, .OrOr, start, line, col); continue; }
|
||||
if has2(self, '=') { op2(self, out, .OrAssign, start, line, col); continue; }
|
||||
op1(self, out, .Or, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '+' {
|
||||
if has2(self, '=') { op2(self, out, .AddAssign, start, line, col); continue; }
|
||||
op1(self, out, .Add, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '-' {
|
||||
if has2(self, '=') { op2(self, out, .SubAssign, start, line, col); continue; }
|
||||
if has2(self, '>') { op2(self, out, .RightArrow, start, line, col); continue; }
|
||||
op1(self, out, .Sub, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '*' {
|
||||
if has2(self, '=') { op2(self, out, .MulAssign, start, line, col); continue; }
|
||||
op1(self, out, .Mul, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '/' {
|
||||
if has2(self, '=') { op2(self, out, .DivAssign, start, line, col); continue; }
|
||||
op1(self, out, .Div, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '%' {
|
||||
if has2(self, '=') { op2(self, out, .ModAssign, start, line, col); continue; }
|
||||
op1(self, out, .Mod, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '^' {
|
||||
if has2(self, '=') { op2(self, out, .XorAssign, start, line, col); continue; }
|
||||
op1(self, out, .Xor, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == ':' {
|
||||
if has2(self, '=') { op2(self, out, .ColonAssign, start, line, col); continue; }
|
||||
op1(self, out, .Colon, start, line, col);
|
||||
continue;
|
||||
}
|
||||
if c == '~' { op1(self, out, .BitNot, start, line, col); continue; }
|
||||
if c == ',' { op1(self, out, .Comma, start, line, col); continue; }
|
||||
if c == ';' { op1(self, out, .Semicolon, start, line, col); continue; }
|
||||
if c == '(' { op1(self, out, .LParen, start, line, col); continue; }
|
||||
if c == ')' { op1(self, out, .RParen, start, line, col); continue; }
|
||||
if c == '[' { op1(self, out, .LBracket, start, line, col); continue; }
|
||||
if c == ']' { op1(self, out, .RBracket, start, line, col); continue; }
|
||||
if c == '{' { op1(self, out, .LBrace, start, line, col); continue; }
|
||||
if c == '}' { op1(self, out, .RBrace, start, line, col); continue; }
|
||||
if c == '#' { op1(self, out, .Sharp, start, line, col); continue; }
|
||||
if c == '@' { op1(self, out, .At, start, line, col); continue; }
|
||||
if c == '$' { op1(self, out, .Dollar, start, line, col); continue; }
|
||||
if c == '?' { op1(self, out, .Cond, start, line, col); continue; }
|
||||
|
||||
report(self, start, start + 1, "unexpected character");
|
||||
emit_to(self, out, .Unknown(c), start, start + 1, line, col);
|
||||
}
|
||||
|
||||
var end = self.src.len;
|
||||
emit_to(self, out, .Eof, end, end, self.line, self.col);
|
||||
}
|
||||
|
||||
fn lex(src: ByteView, diag: *Diag, file: usize): VecResult {
|
||||
var out = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Vec.new(@sizeof(Token), @alignof(Token)) {
|
||||
.Ok(v) => { out = v; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var self = new(src, diag, file);
|
||||
run(&self, &out);
|
||||
ret .Ok(out);
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn view(text: []u8): ByteView {
|
||||
ret ByteView.from_slice(text);
|
||||
}
|
||||
|
||||
fn lex_ok(src: []u8): Vec {
|
||||
var out = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match lex(view(src), null, 0) {
|
||||
.Ok(v) => { out = v; },
|
||||
.Err(error) => { @assert(false, "lex failed"); },
|
||||
}
|
||||
ret out;
|
||||
}
|
||||
|
||||
fn at(v: *Vec, index: usize): Token {
|
||||
match Vec.get(v, index) {
|
||||
.Ok(ptr) => { ret (ptr as *Token).*; },
|
||||
.Err(error) => { ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0); },
|
||||
}
|
||||
ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0);
|
||||
}
|
||||
|
||||
fn expect_punct(tok: Token, expected: Punct) {
|
||||
match tok.kind {
|
||||
.Punct(p) => { @assert(@enum2int(p) == @enum2int(expected), "punct kind"); },
|
||||
_ => { @assert(false, "expected punct"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_keyword(tok: Token, expected: Keyword) {
|
||||
match tok.kind {
|
||||
.Keyword(k) => { @assert(@enum2int(k) == @enum2int(expected), "keyword kind"); },
|
||||
_ => { @assert(false, "expected keyword"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_ident(tok: Token) {
|
||||
match tok.kind {
|
||||
.Ident => {},
|
||||
_ => { @assert(false, "expected ident"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_int(tok: Token, radix: Radix) {
|
||||
match tok.kind {
|
||||
.Int(r) => {
|
||||
@assert(@enum2int(r) == @enum2int(radix), "int radix");
|
||||
},
|
||||
_ => { @assert(false, "expected int"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_char(tok: Token, value: u8) {
|
||||
match tok.kind {
|
||||
.Char(v) => { @assert(v == value, "char value"); },
|
||||
_ => { @assert(false, "expected char"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_endline(tok: Token) {
|
||||
match tok.kind {
|
||||
.Endline => {},
|
||||
_ => { @assert(false, "expected endline"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_eof(tok: Token) {
|
||||
match tok.kind {
|
||||
.Eof => {},
|
||||
_ => { @assert(false, "expected eof"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_text(tok: Token, literal: []u8) {
|
||||
@assert(ByteView.eq(tok.text, view(literal)), "token text");
|
||||
}
|
||||
|
||||
fn expect_pos(tok: Token, line: usize, col: usize) {
|
||||
@assert(tok.line == line && tok.col == col, "token position");
|
||||
}
|
||||
|
||||
fn test_basic() {
|
||||
var v = lex_ok("fn main() {\n ret 0;\n}\n");
|
||||
defer { Vec.drop(&v); }
|
||||
@assert(v.len == 13, "basic token count");
|
||||
|
||||
var t0 = at(&v, 0);
|
||||
expect_keyword(t0, .Fn);
|
||||
expect_text(t0, "fn");
|
||||
expect_pos(t0, 1, 1);
|
||||
expect_ident(at(&v, 1));
|
||||
expect_text(at(&v, 1), "main");
|
||||
expect_punct(at(&v, 2), .LParen);
|
||||
expect_punct(at(&v, 3), .RParen);
|
||||
expect_punct(at(&v, 4), .LBrace);
|
||||
expect_endline(at(&v, 5));
|
||||
expect_keyword(at(&v, 6), .Ret);
|
||||
expect_pos(at(&v, 6), 2, 5);
|
||||
expect_int(at(&v, 7), .Dec);
|
||||
expect_punct(at(&v, 8), .Semicolon);
|
||||
expect_endline(at(&v, 9));
|
||||
expect_punct(at(&v, 10), .RBrace);
|
||||
expect_endline(at(&v, 11));
|
||||
expect_eof(at(&v, 12));
|
||||
}
|
||||
|
||||
fn test_operators() {
|
||||
var v = lex_ok("<-> <- -> <=> <<= << >>= ... .. . := == != < <= > >= && || & | ^ + - * / % ~ ! ? # @ $ , ; : ( ) [ ] { }");
|
||||
defer { Vec.drop(&v); }
|
||||
@assert(v.len == 43, "operator token count");
|
||||
|
||||
expect_punct(at(&v, 0), .ArrowBoth);
|
||||
expect_text(at(&v, 0), "<->");
|
||||
expect_punct(at(&v, 1), .LeftArrow);
|
||||
expect_punct(at(&v, 2), .RightArrow);
|
||||
expect_punct(at(&v, 3), .FatArrowBoth);
|
||||
expect_punct(at(&v, 4), .LShAssign);
|
||||
expect_punct(at(&v, 5), .LSh);
|
||||
expect_text(at(&v, 5), "<<");
|
||||
expect_punct(at(&v, 6), .RShAssign);
|
||||
expect_punct(at(&v, 7), .Ellipsis);
|
||||
expect_punct(at(&v, 8), .Range);
|
||||
expect_punct(at(&v, 9), .Dot);
|
||||
expect_punct(at(&v, 10), .ColonAssign);
|
||||
expect_punct(at(&v, 11), .Eq);
|
||||
expect_punct(at(&v, 12), .Neq);
|
||||
expect_punct(at(&v, 13), .Lt);
|
||||
expect_punct(at(&v, 14), .Le);
|
||||
expect_punct(at(&v, 15), .Gt);
|
||||
expect_punct(at(&v, 16), .Ge);
|
||||
expect_punct(at(&v, 17), .AndAnd);
|
||||
expect_punct(at(&v, 18), .OrOr);
|
||||
expect_punct(at(&v, 19), .And);
|
||||
expect_punct(at(&v, 20), .Or);
|
||||
expect_punct(at(&v, 21), .Xor);
|
||||
expect_punct(at(&v, 22), .Add);
|
||||
expect_punct(at(&v, 23), .Sub);
|
||||
expect_punct(at(&v, 24), .Mul);
|
||||
expect_punct(at(&v, 25), .Div);
|
||||
expect_punct(at(&v, 26), .Mod);
|
||||
expect_punct(at(&v, 27), .BitNot);
|
||||
expect_punct(at(&v, 28), .Not);
|
||||
expect_punct(at(&v, 29), .Cond);
|
||||
expect_punct(at(&v, 30), .Sharp);
|
||||
expect_punct(at(&v, 31), .At);
|
||||
expect_punct(at(&v, 32), .Dollar);
|
||||
expect_punct(at(&v, 33), .Comma);
|
||||
expect_punct(at(&v, 34), .Semicolon);
|
||||
expect_punct(at(&v, 35), .Colon);
|
||||
expect_punct(at(&v, 36), .LParen);
|
||||
expect_punct(at(&v, 37), .RParen);
|
||||
expect_punct(at(&v, 38), .LBracket);
|
||||
expect_punct(at(&v, 39), .RBracket);
|
||||
expect_punct(at(&v, 40), .LBrace);
|
||||
expect_punct(at(&v, 41), .RBrace);
|
||||
expect_eof(at(&v, 42));
|
||||
}
|
||||
|
||||
fn test_comments_and_literals() {
|
||||
var v = lex_ok("// c\n/* a /* nested */ b */\n'a' '\\n' '\\x41' \"hi\\n\" 0xFF 0b1010 0o17 1_000 3.14");
|
||||
defer { Vec.drop(&v); }
|
||||
@assert(v.len == 13, "literal token count");
|
||||
|
||||
var comment = at(&v, 0);
|
||||
match comment.kind {
|
||||
.LineComment => {},
|
||||
_ => { @assert(false, "expected line comment"); },
|
||||
}
|
||||
expect_text(comment, "// c");
|
||||
expect_pos(comment, 1, 1);
|
||||
expect_endline(at(&v, 1));
|
||||
expect_endline(at(&v, 2));
|
||||
|
||||
var ch3 = at(&v, 3);
|
||||
expect_char(ch3, 'a');
|
||||
@assert(ByteView.eq(ch3.text, view("'a'")), "char text");
|
||||
expect_pos(ch3, 3, 1);
|
||||
expect_char(at(&v, 4), '\n');
|
||||
expect_char(at(&v, 5), 0x41);
|
||||
|
||||
var s = at(&v, 6);
|
||||
match s.kind {
|
||||
.Str => {},
|
||||
_ => { @assert(false, "expected string"); },
|
||||
}
|
||||
expect_text(s, "\"hi\\n\"");
|
||||
|
||||
expect_int(at(&v, 7), .Hex);
|
||||
expect_int(at(&v, 8), .Bin);
|
||||
expect_int(at(&v, 9), .Oct);
|
||||
expect_int(at(&v, 10), .Dec);
|
||||
|
||||
var fl = at(&v, 11);
|
||||
match fl.kind {
|
||||
.Float => {},
|
||||
_ => { @assert(false, "expected float"); },
|
||||
}
|
||||
expect_eof(at(&v, 12));
|
||||
}
|
||||
|
||||
// 非法输入走 diag:仍产出 token 以便继续收集错误,但 diag 必须记录。
|
||||
// Rust 风格数字字面量:分隔符下划线 + 多进制 + 小数/指数字面量。
|
||||
fn test_number_forms() {
|
||||
var v = lex_ok("1_000_000 0xFF_FF 0x_ff 0b_1010 0o_7 1_000.5 1.5_0 1_000.500 1.5e-3 1_0.5_0E2");
|
||||
defer { Vec.drop(&v); }
|
||||
@assert(v.len == 11, "number token count");
|
||||
|
||||
expect_int(at(&v, 0), .Dec);
|
||||
expect_text(at(&v, 0), "1_000_000");
|
||||
expect_int(at(&v, 1), .Hex);
|
||||
expect_text(at(&v, 1), "0xFF_FF");
|
||||
expect_int(at(&v, 2), .Hex);
|
||||
expect_text(at(&v, 2), "0x_ff");
|
||||
expect_int(at(&v, 3), .Bin);
|
||||
expect_text(at(&v, 3), "0b_1010");
|
||||
expect_int(at(&v, 4), .Oct);
|
||||
expect_text(at(&v, 4), "0o_7");
|
||||
|
||||
var i: usize = 5;
|
||||
while i < 10 {
|
||||
match at(&v, i).kind {
|
||||
.Float => {},
|
||||
_ => { @assert(false, "expected float"); },
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
expect_text(at(&v, 5), "1_000.5");
|
||||
expect_text(at(&v, 6), "1.5_0");
|
||||
expect_text(at(&v, 7), "1_000.500");
|
||||
expect_text(at(&v, 8), "1.5e-3");
|
||||
expect_text(at(&v, 9), "1_0.5_0E2");
|
||||
expect_eof(at(&v, 10));
|
||||
}
|
||||
|
||||
fn test_diag_errors() {
|
||||
var diag = Diag.new(null);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
match lex(view("'\\q' \"abc"), &diag, 0) {
|
||||
.Ok(v) => { Vec.drop(&v); },
|
||||
.Err(error) => { @assert(false, "lex should return tokens"); },
|
||||
}
|
||||
@assert(Diag.has_error(diag), "lex errors recorded");
|
||||
|
||||
var clean = Diag.new(null);
|
||||
defer { Diag.drop(&clean); }
|
||||
match lex(view("fn main() { ret 0; }"), &clean, 0) {
|
||||
.Ok(v) => { Vec.drop(&v); },
|
||||
.Err(error) => { @assert(false, "legal lex failed"); },
|
||||
}
|
||||
@assert(!Diag.has_error(clean), "legal input has no diagnostics");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_basic();
|
||||
test_operators();
|
||||
test_comments_and_literals();
|
||||
test_number_forms();
|
||||
test_diag_errors();
|
||||
}
|
||||
@@ -0,0 +1,768 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// SPL VM 机器码容器(移植自 stage0/spl_mcode.c 的构建/序列化/dump 部分)。
|
||||
// 只负责「内存中的 prog」与「写入 .sir」,不包含反序列化(由 C 侧 spl_cli 读)。
|
||||
//
|
||||
// 字符串一律是 ByteView(借用上层 IR/attr 的名称,本模块不复制、不释放);
|
||||
// gdata 条目是 ByteBuffer,由本模块拥有并释放。
|
||||
//
|
||||
// 二进制格式(元数据字段均为 u64 小端):
|
||||
// [HEADER] magic(8) nfuncs(8) ninsns(8) nnatives(8) nstrs(8) ndata(8)
|
||||
// [FUNCS] 每项: name_len(8) name(pad8, 含结尾 NUL) idx_of_strtab(8)
|
||||
// nargs(8) ninsns(8) address(8)
|
||||
// [INSTRS] 每项: opcode(2) type(2) imm(8) = 12 字节
|
||||
// [NATIVES] 每项: name_len(8) name(pad8, 含结尾 NUL) idx_of_strtab(8)
|
||||
// [STRTAB] 每项: slen(8) str(pad8)
|
||||
// [GDATA] 每项: dsize(8) data(pad8)
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize, .append_i64 } = @import("utils/fmt.spl");
|
||||
type Mem = @import("sys/mem.spl");
|
||||
type File = @import("sys/file.spl");
|
||||
type .{ .open_cstr, .write_all, .read_all } = @import("utils/io.spl");
|
||||
|
||||
type Op = enum {
|
||||
Error,
|
||||
Push,
|
||||
Dup,
|
||||
Drop,
|
||||
Swap,
|
||||
Pick,
|
||||
Rot,
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
DivS,
|
||||
DivU,
|
||||
RemS,
|
||||
RemU,
|
||||
Neg,
|
||||
And,
|
||||
Or,
|
||||
Xor,
|
||||
Not,
|
||||
Shl,
|
||||
ShrU,
|
||||
ShrS,
|
||||
Eq,
|
||||
Ne,
|
||||
Slt,
|
||||
Sle,
|
||||
Ult,
|
||||
Ule,
|
||||
Sgt,
|
||||
Sge,
|
||||
Ugt,
|
||||
Uge,
|
||||
Trunc,
|
||||
Sext,
|
||||
Zext,
|
||||
Jmp,
|
||||
Bz,
|
||||
Bnz,
|
||||
Halt,
|
||||
Call,
|
||||
Calli,
|
||||
Ret,
|
||||
Alloc,
|
||||
Laddr,
|
||||
Gaddr,
|
||||
Load,
|
||||
Store,
|
||||
Ncall,
|
||||
Nlib,
|
||||
Bk,
|
||||
Dbg,
|
||||
}
|
||||
|
||||
type VmKind = enum {
|
||||
Void,
|
||||
Bool,
|
||||
I8,
|
||||
U8,
|
||||
I16,
|
||||
U16,
|
||||
I32,
|
||||
U32,
|
||||
I64,
|
||||
U64,
|
||||
Isize,
|
||||
Usize,
|
||||
F32,
|
||||
F64,
|
||||
Ptr,
|
||||
}
|
||||
|
||||
type VmIns = struct {
|
||||
opcode: Op,
|
||||
ty: VmKind,
|
||||
imm: usize,
|
||||
}
|
||||
|
||||
type VmFunc = struct {
|
||||
name: ByteView,
|
||||
idx_of_strtab: usize,
|
||||
nargs: usize,
|
||||
ninsns: usize,
|
||||
address: usize,
|
||||
}
|
||||
|
||||
type VmNative = struct {
|
||||
name: ByteView,
|
||||
idx_of_strtab: usize,
|
||||
}
|
||||
|
||||
type Prog = @self;
|
||||
|
||||
insns: Vec, // VmIns
|
||||
funcs: Vec, // VmFunc
|
||||
natives: Vec, // VmNative
|
||||
strtab: Vec, // ByteView
|
||||
gdata: Vec, // ByteBuffer(索引 0 为占位项)
|
||||
|
||||
type ProgResult = enum {
|
||||
Ok: Prog,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn op_name(op: Op): []u8 {
|
||||
match op {
|
||||
.Error => { ret "error"; },
|
||||
.Push => { ret "push"; },
|
||||
.Dup => { ret "dup"; },
|
||||
.Drop => { ret "drop"; },
|
||||
.Swap => { ret "swap"; },
|
||||
.Pick => { ret "pick"; },
|
||||
.Rot => { ret "rot"; },
|
||||
.Add => { ret "add"; },
|
||||
.Sub => { ret "sub"; },
|
||||
.Mul => { ret "mul"; },
|
||||
.DivS => { ret "div_s"; },
|
||||
.DivU => { ret "div_u"; },
|
||||
.RemS => { ret "rem_s"; },
|
||||
.RemU => { ret "rem_u"; },
|
||||
.Neg => { ret "neg"; },
|
||||
.And => { ret "and"; },
|
||||
.Or => { ret "or"; },
|
||||
.Xor => { ret "xor"; },
|
||||
.Not => { ret "not"; },
|
||||
.Shl => { ret "shl"; },
|
||||
.ShrU => { ret "shr_u"; },
|
||||
.ShrS => { ret "shr_s"; },
|
||||
.Eq => { ret "eq"; },
|
||||
.Ne => { ret "ne"; },
|
||||
.Slt => { ret "slt"; },
|
||||
.Sle => { ret "sle"; },
|
||||
.Ult => { ret "ult"; },
|
||||
.Ule => { ret "ule"; },
|
||||
.Sgt => { ret "sgt"; },
|
||||
.Sge => { ret "sge"; },
|
||||
.Ugt => { ret "ugt"; },
|
||||
.Uge => { ret "uge"; },
|
||||
.Trunc => { ret "trunc"; },
|
||||
.Sext => { ret "sext"; },
|
||||
.Zext => { ret "zext"; },
|
||||
.Jmp => { ret "jmp"; },
|
||||
.Bz => { ret "bz"; },
|
||||
.Bnz => { ret "bnz"; },
|
||||
.Halt => { ret "halt"; },
|
||||
.Call => { ret "call"; },
|
||||
.Calli => { ret "calli"; },
|
||||
.Ret => { ret "ret"; },
|
||||
.Alloc => { ret "alloc"; },
|
||||
.Laddr => { ret "laddr"; },
|
||||
.Gaddr => { ret "gaddr"; },
|
||||
.Load => { ret "load"; },
|
||||
.Store => { ret "store"; },
|
||||
.Ncall => { ret "ncall"; },
|
||||
.Nlib => { ret "nlib"; },
|
||||
.Bk => { ret "breakpoint"; },
|
||||
.Dbg => { ret "dbg"; },
|
||||
}
|
||||
ret "error";
|
||||
}
|
||||
|
||||
fn kind_name(k: VmKind): []u8 {
|
||||
match k {
|
||||
.Void => { ret "void"; },
|
||||
.Bool => { ret "bool"; },
|
||||
.I8 => { ret "i8"; },
|
||||
.U8 => { ret "u8"; },
|
||||
.I16 => { ret "i16"; },
|
||||
.U16 => { ret "u16"; },
|
||||
.I32 => { ret "i32"; },
|
||||
.U32 => { ret "u32"; },
|
||||
.I64 => { ret "i64"; },
|
||||
.U64 => { ret "u64"; },
|
||||
.Isize => { ret "isize"; },
|
||||
.Usize => { ret "usize"; },
|
||||
.F32 => { ret "f32"; },
|
||||
.F64 => { ret "f64"; },
|
||||
.Ptr => { ret "ptr"; },
|
||||
}
|
||||
ret "???";
|
||||
}
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
fn new(): ProgResult {
|
||||
var prog = Prog {
|
||||
.insns = Vec.blank(@sizeof(VmIns), @alignof(VmIns)),
|
||||
.funcs = Vec.blank(@sizeof(VmFunc), @alignof(VmFunc)),
|
||||
.natives = Vec.blank(@sizeof(VmNative), @alignof(VmNative)),
|
||||
.strtab = Vec.blank(@sizeof(ByteView), @alignof(ByteView)),
|
||||
.gdata = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
};
|
||||
// 索引 0 占位,使真实数据地址非零。
|
||||
var placeholder = ByteBuffer.new();
|
||||
match Vec.push(&prog.gdata, &placeholder as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&prog.gdata);
|
||||
Vec.drop(&prog.strtab);
|
||||
Vec.drop(&prog.natives);
|
||||
Vec.drop(&prog.funcs);
|
||||
Vec.drop(&prog.insns);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(prog);
|
||||
}
|
||||
|
||||
fn gdata_at(self: *Prog, i: usize): *ByteBuffer {
|
||||
match Vec.get(&self.gdata, i) {
|
||||
.Ok(ptr) => { ret ptr as *ByteBuffer; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn native_at(self: *Prog, i: usize): *VmNative {
|
||||
match Vec.get(&self.natives, i) {
|
||||
.Ok(ptr) => { ret ptr as *VmNative; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn drop(self: *Prog) {
|
||||
var i: usize = 0;
|
||||
while i < self.gdata.len {
|
||||
var e = gdata_at(self, i);
|
||||
if e != null { ByteBuffer.drop(e); }
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.gdata);
|
||||
Vec.drop(&self.strtab);
|
||||
Vec.drop(&self.natives);
|
||||
Vec.drop(&self.funcs);
|
||||
Vec.drop(&self.insns);
|
||||
}
|
||||
|
||||
// ---- 构建 ----
|
||||
|
||||
fn add_instr(self: *Prog, op: Op, ty: VmKind, imm: usize): usize {
|
||||
var i = self.insns.len;
|
||||
if i < self.insns.cap {
|
||||
var p = (self.insns.data as *VmIns) + i;
|
||||
p.opcode = op;
|
||||
p.ty = ty;
|
||||
p.imm = imm;
|
||||
self.insns.len = i + 1;
|
||||
ret i + 1;
|
||||
}
|
||||
var ins = VmIns { .opcode = op, .ty = ty, .imm = imm };
|
||||
if !Vec.push_raw(&self.insns, &ins as *u8) { ret 0; }
|
||||
ret self.insns.len;
|
||||
}
|
||||
|
||||
fn add_func(self: *Prog, name: ByteView, nargs: usize, address: usize): usize {
|
||||
var f = VmFunc {
|
||||
.name = name,
|
||||
.idx_of_strtab = 0,
|
||||
.nargs = nargs,
|
||||
.ninsns = 0,
|
||||
.address = address,
|
||||
};
|
||||
match Vec.push(&self.funcs, &f as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.funcs.len;
|
||||
}
|
||||
|
||||
// 复制 size 字节到新的 gdata 条目;返回其索引(0 表示失败)。
|
||||
// ptr 是上层(Ast/Ast2Ir)拥有的稳定字节,调用期间保持有效。
|
||||
fn add_data(self: *Prog, ptr: usize, size: usize): usize {
|
||||
var entry = ByteBuffer.new();
|
||||
match ByteBuffer.reserve(&entry, size) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&entry); ret 0; },
|
||||
}
|
||||
if size != 0 {
|
||||
if ptr == 0 { ByteBuffer.drop(&entry); ret 0; }
|
||||
ignore_ptr(Mem.copy(entry.storage.data as *any, ptr as *any, size));
|
||||
entry.storage.len = size;
|
||||
}
|
||||
match Vec.push(&self.gdata, &entry as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&entry); ret 0; },
|
||||
}
|
||||
ret self.gdata.len - 1;
|
||||
}
|
||||
|
||||
// 返回 name 对应的 native 索引;不存在则登记。name 为空指针时返回 -1。
|
||||
fn get_or_add_native(self: *Prog, name: ByteView): isize {
|
||||
if name.ptr == null { ret -1; }
|
||||
var i: usize = 0;
|
||||
while i < self.natives.len {
|
||||
var n = native_at(self, i);
|
||||
if n != null && ByteView.eq(n.name, name) { ret i as isize; }
|
||||
i = i + 1;
|
||||
}
|
||||
var nat = VmNative { .name = name, .idx_of_strtab = 0 };
|
||||
match Vec.push(&self.natives, &nat as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret -1; },
|
||||
}
|
||||
ret (self.natives.len - 1) as isize;
|
||||
}
|
||||
|
||||
// ---- dump(输出必须与 stage0 spl_prog_dump 逐字节一致)----
|
||||
|
||||
fn out_s(out: *ByteBuffer, s: []u8): bool {
|
||||
match ByteBuffer.append(out, s) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn out_u(out: *ByteBuffer, v: usize): bool {
|
||||
match append_usize(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn out_i(out: *ByteBuffer, v: i64): bool {
|
||||
match append_i64(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
// %4zd:右对齐、最小宽度 4 的有符号十进制。
|
||||
fn out_width4_i(out: *ByteBuffer, v: i64): bool {
|
||||
var width: usize = 0;
|
||||
if v < 0 {
|
||||
if v >= -9 { width = 2; }
|
||||
else if v >= -99 { width = 3; }
|
||||
else { width = 4; }
|
||||
} else {
|
||||
if v < 10 { width = 1; }
|
||||
else if v < 100 { width = 2; }
|
||||
else if v < 1000 { width = 3; }
|
||||
else { width = 4; }
|
||||
}
|
||||
var pad: usize = 0;
|
||||
if width < 4 { pad = 4 - width; }
|
||||
if !out_s(out, " "[0..pad]) { ret false; }
|
||||
ret out_i(out, v);
|
||||
}
|
||||
|
||||
fn dump(self: *Prog, out: *ByteBuffer): bool {
|
||||
if !out_s(out, ";; SPL bytecode dump: \n") { ret false; }
|
||||
if !out_s(out, ";; ninsns=") { ret false; }
|
||||
if !out_u(out, self.insns.len) { ret false; }
|
||||
if !out_s(out, " nfuncs=") { ret false; }
|
||||
if !out_u(out, self.funcs.len) { ret false; }
|
||||
if !out_s(out, " nnatives=") { ret false; }
|
||||
if !out_u(out, self.natives.len) { ret false; }
|
||||
if !out_s(out, " nstrs=") { ret false; }
|
||||
if !out_u(out, self.strtab.len) { ret false; }
|
||||
if !out_s(out, "\n\n") { ret false; }
|
||||
|
||||
if self.funcs.len > 0 {
|
||||
if !out_s(out, ";; --- functions ---\n") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < self.funcs.len {
|
||||
var f = func_at(self, i);
|
||||
if f == null {
|
||||
@assert(false, "func missing in dump");
|
||||
ret false;
|
||||
}
|
||||
if !out_s(out, " ") { ret false; }
|
||||
if !out_s(out, ByteView.as_slice(f.name)) { ret false; }
|
||||
if !out_s(out, " nargs=") { ret false; }
|
||||
if !out_u(out, f.nargs) { ret false; }
|
||||
if !out_s(out, " ninsns=") { ret false; }
|
||||
if !out_u(out, f.ninsns) { ret false; }
|
||||
if !out_s(out, " addr=") { ret false; }
|
||||
if !out_u(out, f.address) { ret false; }
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
}
|
||||
|
||||
if self.natives.len > 0 {
|
||||
if !out_s(out, ";; --- natives ---\n") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < self.natives.len {
|
||||
var n = native_at(self, i);
|
||||
if n == null {
|
||||
@assert(false, "native missing in dump");
|
||||
ret false;
|
||||
}
|
||||
if !out_s(out, " ") { ret false; }
|
||||
if !out_s(out, ByteView.as_slice(n.name)) { ret false; }
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
}
|
||||
|
||||
if self.strtab.len > 0 {
|
||||
if !out_s(out, ";; --- string table ---\n") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < self.strtab.len {
|
||||
var s = str_at(self, i);
|
||||
if !out_s(out, " ") { ret false; }
|
||||
if !out_i(out, i as i64) { ret false; }
|
||||
if !out_s(out, ": \"") { ret false; }
|
||||
if !out_s(out, ByteView.as_slice(s)) { ret false; }
|
||||
if !out_s(out, "\"\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
}
|
||||
|
||||
if !out_s(out, ";; --- instructions ---\n") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < self.insns.len {
|
||||
var ins = ins_at(self, i);
|
||||
if !out_width4_i(out, i as i64) { ret false; }
|
||||
if !out_s(out, ": ") { ret false; }
|
||||
if !out_s(out, op_name(ins.opcode)) { ret false; }
|
||||
if @enum2int(ins.ty) != @enum2int(VmKind.Void) {
|
||||
if !out_s(out, " ") { ret false; }
|
||||
if !out_s(out, kind_name(ins.ty)) { ret false; }
|
||||
}
|
||||
if !out_s(out, " ") { ret false; }
|
||||
if !out_i(out, ins.imm as i64) { ret false; }
|
||||
|
||||
if @enum2int(ins.opcode) == @enum2int(Op.Jmp) ||
|
||||
@enum2int(ins.opcode) == @enum2int(Op.Bz) ||
|
||||
@enum2int(ins.opcode) == @enum2int(Op.Bnz) {
|
||||
if !out_s(out, " ; -> ") { ret false; }
|
||||
if !out_u(out, i + 1 + ins.imm) { ret false; }
|
||||
} else if @enum2int(ins.opcode) == @enum2int(Op.Call) {
|
||||
if !out_s(out, " ; nargs=") { ret false; }
|
||||
if !out_u(out, ins.imm) { ret false; }
|
||||
if !out_s(out, ", from stack") { ret false; }
|
||||
} else if @enum2int(ins.opcode) == @enum2int(Op.Calli) {
|
||||
if !out_s(out, " ; indirect call") { ret false; }
|
||||
} else if @enum2int(ins.opcode) == @enum2int(Op.Gaddr) {
|
||||
if !out_s(out, " ; gdata[") { ret false; }
|
||||
if !out_u(out, ins.imm) { ret false; }
|
||||
if !out_s(out, "]") { ret false; }
|
||||
}
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn ins_at(self: *Prog, i: usize): VmIns {
|
||||
if i >= self.insns.len { ret VmIns { .opcode = .Error, .ty = .Void, .imm = 0 }; }
|
||||
ret ((self.insns.data as *VmIns) + i).*;
|
||||
}
|
||||
|
||||
fn func_at(self: *Prog, i: usize): *VmFunc {
|
||||
match Vec.get(&self.funcs, i) {
|
||||
.Ok(ptr) => { ret ptr as *VmFunc; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn str_at(self: *Prog, i: usize): ByteView {
|
||||
match Vec.get(&self.strtab, i) {
|
||||
.Ok(ptr) => { ret (ptr as *ByteView).*; },
|
||||
.Err(error) => { ret ByteView.empty(); },
|
||||
}
|
||||
ret ByteView.empty();
|
||||
}
|
||||
|
||||
// ---- 序列化 ----
|
||||
|
||||
fn align8(n: usize): usize {
|
||||
ret (n + 7) & (~(7 as usize));
|
||||
}
|
||||
|
||||
// 说明:以下 put_* 直接写入已预留容量的 ByteBuffer 存储(store 已按精确总大小 reserve),
|
||||
// 跳过 ByteBuffer.append → Vec.append 的校验/拷贝路径。若容量不足会越界,故 store 先校验容量。
|
||||
|
||||
fn put_u8(out: *ByteBuffer, b: u8): bool {
|
||||
out.storage.data[out.storage.len] = b;
|
||||
out.storage.len = out.storage.len + 1;
|
||||
ret true;
|
||||
}
|
||||
|
||||
// 直接按小端整型写(x86 小端;VM 的 STORE 用 memcpy,未对齐也安全)。
|
||||
fn put_u16(out: *ByteBuffer, v: usize): bool {
|
||||
var d = out.storage.data + out.storage.len;
|
||||
(d as *u16).* = v as u16;
|
||||
out.storage.len = out.storage.len + 2;
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_u64(out: *ByteBuffer, v: usize): bool {
|
||||
var d = out.storage.data + out.storage.len;
|
||||
(d as *u64).* = v as u64;
|
||||
out.storage.len = out.storage.len + 8;
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_zeros(out: *ByteBuffer, n: usize): bool {
|
||||
if n != 0 {
|
||||
ignore_ptr(Mem.set((out.storage.data + out.storage.len) as *any, 0, n));
|
||||
out.storage.len = out.storage.len + n;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn put_view(out: *ByteBuffer, v: ByteView): bool {
|
||||
if v.len == 0 { ret true; }
|
||||
Mem.copy((out.storage.data + out.storage.len) as *any, v.ptr as *any, v.len);
|
||||
out.storage.len = out.storage.len + v.len;
|
||||
ret true;
|
||||
}
|
||||
|
||||
// 写入「带结尾 NUL、再按 8 对齐」的名称字段(对应 C 侧 nlen = strlen+1)。
|
||||
fn put_name_field(out: *ByteBuffer, name: ByteView): bool {
|
||||
var nlen = name.len + 1;
|
||||
if !put_u64(out, nlen) { ret false; }
|
||||
if !put_view(out, name) { ret false; }
|
||||
if !put_u8(out, 0) { ret false; }
|
||||
ret put_zeros(out, align8(nlen) - nlen);
|
||||
}
|
||||
|
||||
fn store(self: *Prog, path: ByteView): Status {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
|
||||
// 精确预估输出大小并一次性预留,避免大文件逐字节增长导致的重复拷贝。
|
||||
var total: usize = 48;
|
||||
var fi: usize = 0;
|
||||
while fi < self.funcs.len {
|
||||
var f = func_at(self, fi);
|
||||
if f != null {
|
||||
var nlen = f.name.len + 1;
|
||||
total = total + 8 + align8(nlen) + 32;
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
total = total + self.insns.len * 12;
|
||||
fi = 0;
|
||||
while fi < self.natives.len {
|
||||
var n = native_at(self, fi);
|
||||
if n != null {
|
||||
var nlen = n.name.len + 1;
|
||||
total = total + 8 + align8(nlen) + 8;
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
fi = 0;
|
||||
while fi < self.strtab.len {
|
||||
total = total + 8 + align8(str_at(self, fi).len);
|
||||
fi = fi + 1;
|
||||
}
|
||||
fi = 1;
|
||||
while fi < self.gdata.len {
|
||||
var e = gdata_at(self, fi);
|
||||
if e != null { total = total + 8 + align8(e.storage.len); }
|
||||
fi = fi + 1;
|
||||
}
|
||||
match ByteBuffer.reserve(&out, total) { .Ok => {}, .Err(error) => {} }
|
||||
if out.storage.cap < total || out.storage.data == null { ret .Err(make(.Io)); }
|
||||
|
||||
if !put_view(&out, ByteView.from_slice("SPLBIN")) { ret .Err(make(.Io)); }
|
||||
if !put_zeros(&out, 2) { ret .Err(make(.Io)); }
|
||||
|
||||
var nfuncs = self.funcs.len;
|
||||
var ninsns = self.insns.len;
|
||||
var nnatives = self.natives.len;
|
||||
var nstrs = self.strtab.len;
|
||||
var ndata: usize = 0;
|
||||
if self.gdata.len > 0 { ndata = self.gdata.len - 1; }
|
||||
|
||||
if !put_u64(&out, nfuncs) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, ninsns) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, nnatives) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, nstrs) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, ndata) { ret .Err(make(.Io)); }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < nfuncs {
|
||||
var f = func_at(self, i);
|
||||
if f == null { ret .Err(make(.Io)); }
|
||||
if !put_name_field(&out, f.name) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, f.idx_of_strtab) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, f.nargs) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, f.ninsns) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, f.address) { ret .Err(make(.Io)); }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < ninsns {
|
||||
var ins = ins_at(self, i);
|
||||
var d = out.storage.data + out.storage.len;
|
||||
(d as *u16).* = @enum2int(ins.opcode) as u16;
|
||||
((d + 2) as *u16).* = @enum2int(ins.ty) as u16;
|
||||
((d + 4) as *u64).* = ins.imm as u64;
|
||||
out.storage.len = out.storage.len + 12;
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < nnatives {
|
||||
var n = native_at(self, i);
|
||||
if n == null { ret .Err(make(.Io)); }
|
||||
if !put_name_field(&out, n.name) { ret .Err(make(.Io)); }
|
||||
if !put_u64(&out, n.idx_of_strtab) { ret .Err(make(.Io)); }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < nstrs {
|
||||
var s = str_at(self, i);
|
||||
if !put_u64(&out, s.len) { ret .Err(make(.Io)); }
|
||||
if !put_view(&out, s) { ret .Err(make(.Io)); }
|
||||
if !put_zeros(&out, align8(s.len) - s.len) { ret .Err(make(.Io)); }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 1;
|
||||
while i < self.gdata.len {
|
||||
var e = gdata_at(self, i);
|
||||
if e == null { ret .Err(make(.Io)); }
|
||||
var dsize = e.storage.len;
|
||||
if !put_u64(&out, dsize) { ret .Err(make(.Io)); }
|
||||
if !put_view(&out, ByteView.from_slice(ByteBuffer.as_slice(e.*))) { ret .Err(make(.Io)); }
|
||||
if !put_zeros(&out, align8(dsize) - dsize) { ret .Err(make(.Io)); }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var f = open_cstr(path, ByteView.from_slice("wb"));
|
||||
if !File.valid(&f) { ret .Err(make(.Io)); }
|
||||
var w = write_all(&f, ByteBuffer.as_slice(out));
|
||||
var closed = File.close(&f);
|
||||
match w {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if closed != 0 { ret .Err(make(.Io)); }
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn ignore_ptr(p: *any) {}
|
||||
fn ignore_usize(v: usize) {}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_dump_basic() {
|
||||
var pr: Prog = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { pr = v; },
|
||||
.Err(error) => { @assert(false, "prog init"); ret; },
|
||||
}
|
||||
defer { drop(&pr); }
|
||||
|
||||
ignore_usize(add_instr(&pr, .Halt, .Void, 0));
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump(&pr, &out) { @assert(false, "dump"); ret; }
|
||||
|
||||
var expected: [69]u8 = .[
|
||||
';', ';', ' ', 'S', 'P', 'L', ' ', 'b',
|
||||
'y', 't', 'e', 'c', 'o', 'd', 'e', ' ',
|
||||
'd', 'u', 'm', 'p', ':', ' ', 10,
|
||||
';', ';', ' ', 'n', 'i', 'n', 's', 'n',
|
||||
's', '=', '1', ' ', 'n', 'f', 'u', 'n',
|
||||
'c', 's', '=', '0', ' ', 'n', 'n', 'a',
|
||||
't', 'i', 'v', 'e', 's', '=', '0', ' ',
|
||||
'n', 's', 't', 'r', 's', '=', '0', 10, 10,
|
||||
';', ';', ' ', '-', '-', '-',
|
||||
];
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
@assert(got.len > expected.len, "dump longer than header prefix");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(got.ptr[i] == expected[i], "dump header bytes");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_native_dedup() {
|
||||
var pr: Prog = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { pr = v; },
|
||||
.Err(error) => { @assert(false, "prog init"); ret; },
|
||||
}
|
||||
defer { drop(&pr); }
|
||||
|
||||
var vm = ByteView.from_slice("vm_printf");
|
||||
@assert(get_or_add_native(&pr, vm) == 0, "first native idx");
|
||||
@assert(get_or_add_native(&pr, vm) == 0, "dedup native idx");
|
||||
@assert(pr.natives.len == 1, "native count");
|
||||
}
|
||||
|
||||
fn test_store_roundtrip() {
|
||||
var pr: Prog = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { pr = v; },
|
||||
.Err(error) => { @assert(false, "prog init"); ret; },
|
||||
}
|
||||
defer { drop(&pr); }
|
||||
|
||||
ignore_usize(add_func(&pr, ByteView.from_slice("main"), 0, 0));
|
||||
ignore_usize(add_instr(&pr, .Halt, .Void, 0));
|
||||
|
||||
var path = ByteView.from_slice("build/_mcode_roundtrip.tmp");
|
||||
match store(&pr, path) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "store to file"); ret; },
|
||||
}
|
||||
|
||||
var data = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&data); }
|
||||
match read_all(path, &data) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "read back"); ret; },
|
||||
}
|
||||
var b = ByteBuffer.as_slice(data);
|
||||
// header(48) + 一个函数项(8 + 8 + 4*8 = 48) + 一条指令(12) = 108
|
||||
@assert(b.len == 108, "serialized size");
|
||||
@assert(b.ptr[0] == 'S' && b.ptr[1] == 'P' && b.ptr[2] == 'L' && b.ptr[3] == 'B' &&
|
||||
b.ptr[4] == 'I' && b.ptr[5] == 'N' && b.ptr[6] == 0 && b.ptr[7] == 0,
|
||||
"magic");
|
||||
@assert(b.ptr[8] == 1, "nfuncs");
|
||||
@assert(b.ptr[16] == 1, "ninsns");
|
||||
@assert(b.ptr[24] == 0 && b.ptr[32] == 0 && b.ptr[40] == 0, "nnatives/nstrs/ndata");
|
||||
// 函数项起始:name_len(5) + "main\0" + pad(3)
|
||||
@assert(b.ptr[48] == 5, "name_len");
|
||||
@assert(b.ptr[56] == 'm' && b.ptr[57] == 'a' && b.ptr[58] == 'i' && b.ptr[59] == 'n' &&
|
||||
b.ptr[60] == 0, "name bytes");
|
||||
@assert(b.ptr[61] == 0 && b.ptr[62] == 0 && b.ptr[63] == 0, "name padding");
|
||||
// 指令:opcode(2)=halt(38) type(2)=void(0) imm(8)=0
|
||||
var ins_off: usize = 96;
|
||||
@assert(b.ptr[ins_off + 0] == 38 && b.ptr[ins_off + 1] == 0, "opcode bytes");
|
||||
@assert(b.ptr[ins_off + 2] == 0 && b.ptr[ins_off + 3] == 0, "type bytes");
|
||||
@assert(b.ptr[ins_off + 4] == 0 && b.ptr[ins_off + 11] == 0, "imm bytes");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_dump_basic();
|
||||
test_native_dedup();
|
||||
test_store_roundtrip();
|
||||
}
|
||||
+2404
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,220 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 作用域树(移植自 stage1/spl_scope.c)。每个作用域是一张名字 -> Symbol 表,
|
||||
// 节点用 parent 指向父作用域;查找从 current_scope 沿父链上溯(支持遮蔽)。
|
||||
// 名字是 ByteView(借用源/AST);Map 复制键值字节,但 ByteView 指向的字节由
|
||||
// 调用方保证存活。node 字段是 def.spl 的 DefId。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map, .hash_kind_view } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .hash_view, .eq_view } = @import("utils/hash.spl");
|
||||
|
||||
type SymbolKind = enum {
|
||||
Error,
|
||||
Var,
|
||||
Member,
|
||||
Fn,
|
||||
Type,
|
||||
}
|
||||
|
||||
type Symbol = struct {
|
||||
name: ByteView,
|
||||
kind: SymbolKind,
|
||||
node: usize, // DefId
|
||||
}
|
||||
|
||||
type ScopeNode = struct {
|
||||
parent: usize,
|
||||
symbols: Map,
|
||||
}
|
||||
|
||||
type Scope = @self;
|
||||
|
||||
scopes: Vec, // ScopeNode
|
||||
root_scope: usize,
|
||||
current_scope: usize,
|
||||
|
||||
type ScopeResult = enum {
|
||||
Ok: Scope,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new_map(): Map {
|
||||
ret Map.new(@sizeof(ByteView), @alignof(ByteView), @sizeof(Symbol), @alignof(Symbol),
|
||||
hash_kind_view());
|
||||
}
|
||||
|
||||
fn new(): ScopeResult {
|
||||
var sc = Scope {
|
||||
.scopes = Vec.blank(@sizeof(ScopeNode), @alignof(ScopeNode)),
|
||||
.root_scope = 0,
|
||||
.current_scope = 0,
|
||||
};
|
||||
var sentinel = ScopeNode { .parent = 0, .symbols = new_map() };
|
||||
match Vec.push(&sc.scopes, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&sc.scopes);
|
||||
Map.drop(&sentinel.symbols);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(sc);
|
||||
}
|
||||
|
||||
fn drop(self: *Scope) {
|
||||
var i: usize = 0;
|
||||
while i < self.scopes.len {
|
||||
var n = node(self, i);
|
||||
if n != null {
|
||||
Map.drop(&n.symbols);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.scopes);
|
||||
self.root_scope = 0;
|
||||
self.current_scope = 0;
|
||||
}
|
||||
|
||||
// id 0 与越界返回 null。
|
||||
fn node(self: *Scope, id: usize): *ScopeNode {
|
||||
if id == 0 || id >= self.scopes.len { ret null; }
|
||||
ret (self.scopes.data as *ScopeNode) + id;
|
||||
}
|
||||
|
||||
// 新作用域 parent = 当前作用域;不自动切换 current(调用方负责,见 sema)。
|
||||
fn alloc(self: *Scope): usize {
|
||||
var n = ScopeNode { .parent = self.current_scope, .symbols = new_map() };
|
||||
match Vec.push(&self.scopes, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Map.drop(&n.symbols);
|
||||
ret 0;
|
||||
},
|
||||
}
|
||||
ret self.scopes.len - 1;
|
||||
}
|
||||
|
||||
// 插入符号;同名已存在返回 false(不改动)。
|
||||
fn insert(self: *Scope, id: usize, symbol: Symbol): bool {
|
||||
if id == 0 || id >= self.scopes.len { ret false; }
|
||||
var n = node(self, id);
|
||||
if n == null { ret false; }
|
||||
var old = Symbol {
|
||||
.name = ByteView.empty(),
|
||||
.kind = .Error,
|
||||
.node = 0,
|
||||
};
|
||||
match Map.get(&n.symbols, &symbol.name as *u8, &old as *u8) {
|
||||
.Found => { ret false; },
|
||||
.NotFound => {},
|
||||
}
|
||||
match Map.put(&n.symbols, &symbol.name as *u8, &symbol as *u8) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 从 current_scope 沿父链查找;找到填 out 返回 true。
|
||||
fn find(self: *Scope, name: ByteView, out: *Symbol): bool {
|
||||
if out == null { ret false; }
|
||||
var id = self.current_scope;
|
||||
while id != 0 {
|
||||
var n = node(self, id);
|
||||
if n == null { ret false; }
|
||||
match Map.get(&n.symbols, &name as *u8, out as *u8) {
|
||||
.Found => { ret true; },
|
||||
.NotFound => {},
|
||||
}
|
||||
id = n.parent;
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn sym(name: []u8, kind: SymbolKind, node: usize): Symbol {
|
||||
ret Symbol {
|
||||
.name = ByteView.from_slice(name),
|
||||
.kind = kind,
|
||||
.node = node,
|
||||
};
|
||||
}
|
||||
|
||||
fn test_alloc_and_find() {
|
||||
var sc: Scope = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { sc = v; },
|
||||
.Err(error) => { @assert(false, "scope new failed"); },
|
||||
}
|
||||
defer { drop(&sc); }
|
||||
|
||||
@assert(sc.root_scope == 0 && sc.current_scope == 0, "initial");
|
||||
|
||||
// root
|
||||
var root = alloc(&sc);
|
||||
sc.root_scope = root;
|
||||
sc.current_scope = root;
|
||||
|
||||
// module scope(child of root)
|
||||
var mod = alloc(&sc);
|
||||
sc.current_scope = mod;
|
||||
|
||||
@assert(insert(&sc, mod, sym("x", .Var, 11)), "insert x");
|
||||
@assert(!insert(&sc, mod, sym("x", .Var, 12)), "duplicate x");
|
||||
|
||||
// 函数作用域
|
||||
var fn_scope = alloc(&sc);
|
||||
sc.current_scope = fn_scope;
|
||||
|
||||
var out = sym("", .Error, 0);
|
||||
@assert(find(&sc, ByteView.from_slice("x"), &out), "find x from child");
|
||||
@assert(out.node == 11, "x def id");
|
||||
|
||||
// 遮蔽:函数内层再定义 x
|
||||
@assert(insert(&sc, fn_scope, sym("x", .Fn, 99)), "shadow x");
|
||||
@assert(find(&sc, ByteView.from_slice("x"), &out), "find shadow");
|
||||
@assert(out.node == 99, "shadow def id");
|
||||
|
||||
// 未定义
|
||||
@assert(!find(&sc, ByteView.from_slice("nope"), &out), "missing");
|
||||
|
||||
// 父链结构
|
||||
var m = node(&sc, mod);
|
||||
var f = node(&sc, fn_scope);
|
||||
@assert(m != null && m.parent == root, "mod parent");
|
||||
@assert(f != null && f.parent == mod, "fn parent");
|
||||
}
|
||||
|
||||
fn test_kinds_isolated() {
|
||||
var sc: Scope = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { sc = v; },
|
||||
.Err(error) => { @assert(false, "scope new failed"); },
|
||||
}
|
||||
defer { drop(&sc); }
|
||||
|
||||
var a = alloc(&sc);
|
||||
sc.current_scope = a;
|
||||
@assert(insert(&sc, a, sym("T", .Type, 1)), "type T");
|
||||
@assert(insert(&sc, a, sym("f", .Fn, 2)), "fn f");
|
||||
|
||||
var out = sym("", .Error, 0);
|
||||
@assert(find(&sc, ByteView.from_slice("T"), &out), "find T");
|
||||
var kind_matches = false;
|
||||
match out.kind {
|
||||
.Type => { kind_matches = true; },
|
||||
_ => {},
|
||||
}
|
||||
@assert(kind_matches, "T kind");
|
||||
@assert(find(&sc, ByteView.from_slice("f"), &out), "find f");
|
||||
@assert(out.node == 2, "f def id");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_alloc_and_find();
|
||||
test_kinds_isolated();
|
||||
}
|
||||
+3085
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,838 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// splc1 (stage2 自举编译器入口)。
|
||||
// 当前实现 `--dump <lex|ast|sema|ir|vm> <file> [out]`:读取源文件,按与 stage1
|
||||
// splc0 完全一致的格式打印各阶段产物,供 diff 测试逐字节比对。
|
||||
|
||||
type .{ .ByteView } = @import("./utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("./utils/byte_buffer.spl");
|
||||
type .{ .Vec } = @import("./utils/vec.spl");
|
||||
type .{ .Token, .kind_name } = @import("./token.spl");
|
||||
type .{ .Lexer } = @import("./lexer.spl");
|
||||
type Ast = @import("./ast.spl");
|
||||
type Parser = @import("./parser.spl");
|
||||
type Sema = @import("./sema.spl");
|
||||
type .{ .append_usize } = @import("./utils/fmt.spl");
|
||||
type .{ .read_all, .print, .eprint, .open_cstr, .write_all } = @import("./utils/io.spl");
|
||||
type File = @import("./sys/file.spl");
|
||||
type .{ .Sources, .Source, .set_new, .set_add, .set_at, .set_drop } = @import("./utils/source.spl");
|
||||
type .{ .Diag, .Code, .span_none } = @import("./diag.spl");
|
||||
type Logger = @import("./utils/log.spl");
|
||||
type .{ .Loader, .LoadResult } = @import("./sema.spl");
|
||||
type IrBuilder = @import("./ir_builder.spl");
|
||||
type Ir = @import("./ir.spl");
|
||||
type Ast2Ir = @import("./ast2ir.spl");
|
||||
type LayoutM = @import("./layout.spl");
|
||||
type .{ .Layout } = @import("./layout.spl");
|
||||
type Ir2VmM = @import("./ir2vm.spl");
|
||||
type .{ .Ir2Vm } = @import("./ir2vm.spl");
|
||||
type Ir2CM = @import("./ir2c.spl");
|
||||
type .{ .alloc, .free } = @import("./sys/mem.spl");
|
||||
|
||||
fn token_at(tokens: *Vec, index: usize): Token {
|
||||
match Vec.get(tokens, index) {
|
||||
.Ok(ptr) => { ret (ptr as *Token).*; },
|
||||
.Err(error) => { ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0); },
|
||||
}
|
||||
ret Token.make(.Unknown(0), ByteView.empty(), 0, 0, 0);
|
||||
}
|
||||
|
||||
// 追加成功返回 true;失败(仅 OOM)返回 false。
|
||||
fn push(out: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(out, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_byte(out: *ByteBuffer, byte: u8): bool {
|
||||
match ByteBuffer.append_byte(out, byte) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(out: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(out, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 输出格式与 splc0.c 的 dump_tokens 一致:
|
||||
// tokens got (N)\n
|
||||
// [TOK_TYPE] <lexeme> (line:col)\n
|
||||
fn dump_tokens(tokens: *Vec): bool {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
|
||||
if !push(&out, "tokens got (") { ret false; }
|
||||
if !push_usize(&out, tokens.len) { ret false; }
|
||||
if !push(&out, ")\n") { ret false; }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < tokens.len {
|
||||
var tok = token_at(tokens, i);
|
||||
if !push(&out, "[") { ret false; }
|
||||
if !push(&out, kind_name(tok.kind)) { ret false; }
|
||||
if !push(&out, "] ") { ret false; }
|
||||
if !push(&out, ByteView.as_slice(tok.text)) { ret false; }
|
||||
if !push(&out, " (") { ret false; }
|
||||
if !push_usize(&out, tok.line) { ret false; }
|
||||
if !push_byte(&out, ':') { ret false; }
|
||||
if !push_usize(&out, tok.col) { ret false; }
|
||||
if !push(&out, ")\n") { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
match print(ByteBuffer.as_slice(out)) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump_lex(log: *Logger, path: ByteView): i32 {
|
||||
var content = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&content); }
|
||||
match read_all(path, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot read input file");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
match set_add(&sources, path, text) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot register source");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
|
||||
var diag = Diag.new(&sources);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, &diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "lex failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Vec.drop(&tokens); }
|
||||
|
||||
// 非法输入:走诊断(stage1 在此处是宽容无视,stage2 显式报错)。
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
if !dump_tokens(&tokens) {
|
||||
Logger.fatal(log, "out of memory while dumping tokens");
|
||||
ret 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn dump_ast(log: *Logger, path: ByteView): i32 {
|
||||
var content = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&content); }
|
||||
match read_all(path, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot read input file");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
match set_add(&sources, path, text) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot register source");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var diag = Diag.new(&sources);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, &diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "lex failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Vec.drop(&tokens); }
|
||||
|
||||
var ast: Ast = undefined;
|
||||
match Ast.new(&sources) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "ast init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Ast.drop(&ast); }
|
||||
|
||||
var p = Parser.new(&ast, tokens, &diag);
|
||||
Parser.parse(&p);
|
||||
|
||||
if Diag.has_error(diag) || p.failed {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !Ast.dump(&ast, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping ast");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(out)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// ---- @import 模块加载器(driver 侧)----
|
||||
|
||||
type ModEntry = struct {
|
||||
path: ByteView,
|
||||
ast: *Ast,
|
||||
ok: bool,
|
||||
}
|
||||
|
||||
type LoaderCtx = struct {
|
||||
sources: *Sources,
|
||||
mods: Vec, // ModEntry
|
||||
buffers: Vec, // ByteBuffer(路径/文本稳定存储)
|
||||
diag: *Diag,
|
||||
}
|
||||
|
||||
fn ignore_bool(b: bool) {}
|
||||
|
||||
fn append_bytes(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn append_byte(buf: *ByteBuffer, b: u8): bool {
|
||||
match ByteBuffer.append_byte(buf, b) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn sub(p: ByteView, a: usize, b: usize): []u8 {
|
||||
ret ByteView.as_slice(p)[a..b];
|
||||
}
|
||||
|
||||
// 处理 "..":弹出 out 的最后一个路径分量;无分量可弹(空 / 根 / 末段已是 "..")时,
|
||||
// 退化为追加 ".."(相对路径的语义),绝对根则忽略。这样 "../x" 与规范路径能归一到同一模块。
|
||||
fn normalize_pop(out: *ByteBuffer) {
|
||||
var cur = ByteBuffer.as_slice(out.*);
|
||||
if cur.len == 0 {
|
||||
ignore_bool(append_bytes(out, ".."));
|
||||
ret;
|
||||
}
|
||||
var end = cur.len;
|
||||
if cur.ptr[end - 1] == '/' {
|
||||
if end == 1 { ret; } // 绝对根 "/":忽略
|
||||
end = end - 1;
|
||||
}
|
||||
var start = end;
|
||||
while start > 0 && cur.ptr[start - 1] != '/' { start = start - 1; }
|
||||
if end - start == 2 && cur.ptr[start] == '.' && cur.ptr[start + 1] == '.' {
|
||||
ignore_bool(append_bytes(out, ".."));
|
||||
ret;
|
||||
}
|
||||
var new_len: usize = 0;
|
||||
if start > 0 { new_len = start - 1; }
|
||||
if new_len == 0 && cur.ptr[0] == '/' { new_len = 1; }
|
||||
out.*.storage.len = new_len;
|
||||
}
|
||||
|
||||
// 折叠重复分隔符、解析 "." 与 ".."、'\' 统一成 '/'。
|
||||
fn normalize_into(out: *ByteBuffer, p: ByteView) {
|
||||
if p.len > 0 && (p.ptr[0] == '/' || p.ptr[0] == '\\') {
|
||||
ignore_bool(append_byte(out, '/'));
|
||||
}
|
||||
var i: usize = 0;
|
||||
while i < p.len {
|
||||
while i < p.len && (p.ptr[i] == '/' || p.ptr[i] == '\\') { i = i + 1; }
|
||||
if i >= p.len { break; }
|
||||
var start = i;
|
||||
while i < p.len && p.ptr[i] != '/' && p.ptr[i] != '\\' { i = i + 1; }
|
||||
var seg = sub(p, start, i);
|
||||
if seg.len == 1 && seg.ptr[0] == '.' { continue; }
|
||||
if seg.len == 2 && seg.ptr[0] == '.' && seg.ptr[1] == '.' {
|
||||
normalize_pop(out);
|
||||
continue;
|
||||
}
|
||||
var cur = ByteBuffer.as_slice(out.*);
|
||||
if cur.len > 0 && cur.ptr[cur.len - 1] != '/' {
|
||||
ignore_bool(append_byte(out, '/'));
|
||||
}
|
||||
ignore_bool(append_bytes(out, seg));
|
||||
}
|
||||
}
|
||||
|
||||
// 把 path 拼到 from 所在目录,并规范化;绝对路径直通。
|
||||
fn resolve_path(from: ByteView, path: ByteView): ByteBuffer {
|
||||
var out = ByteBuffer.new();
|
||||
var absolute = false;
|
||||
if path.len > 0 && (path.ptr[0] == '/' || path.ptr[0] == '\\') { absolute = true; }
|
||||
if path.len >= 2 && path.ptr[1] == ':' { absolute = true; }
|
||||
if absolute {
|
||||
normalize_into(&out, path);
|
||||
ret out;
|
||||
}
|
||||
var last: isize = -1;
|
||||
var i: usize = 0;
|
||||
while i < from.len {
|
||||
if from.ptr[i] == '/' || from.ptr[i] == '\\' { last = i as isize; }
|
||||
i = i + 1;
|
||||
}
|
||||
if last >= 0 {
|
||||
ignore_bool(append_bytes(&out, sub(from, 0, (last as usize) + 1)));
|
||||
}
|
||||
ignore_bool(append_bytes(&out, ByteView.as_slice(path)));
|
||||
var norm = ByteBuffer.new();
|
||||
normalize_into(&norm, ByteView.from_slice(ByteBuffer.as_slice(out)));
|
||||
ByteBuffer.drop(&out);
|
||||
ret norm;
|
||||
}
|
||||
|
||||
fn mod_at(ctx: *LoaderCtx, i: usize): *ModEntry {
|
||||
match Vec.get(&ctx.mods, i) {
|
||||
.Ok(ptr) => { ret ptr as *ModEntry; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn driver_load(user: *any, from: ByteView, path: ByteView, diag: *Diag): LoadResult {
|
||||
var ctx = user as *LoaderCtx;
|
||||
var absbuf = resolve_path(from, path);
|
||||
var abs = ByteView.from_slice(ByteBuffer.as_slice(absbuf));
|
||||
|
||||
var i: usize = 0;
|
||||
while i < ctx.mods.len {
|
||||
var m = mod_at(ctx, i);
|
||||
if m != null && ByteView.eq(m.path, abs) {
|
||||
ByteBuffer.drop(&absbuf);
|
||||
if !m.ok {
|
||||
Diag.err(diag, span_none(), .CircularDep, "@import: circular import");
|
||||
ret .Missing;
|
||||
}
|
||||
ret .Ok(m.ast);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var midx = ctx.mods.len;
|
||||
var placeholder = ModEntry { .path = abs, .ast = null, .ok = false };
|
||||
match Vec.push(&ctx.mods, &placeholder as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&absbuf); ret .Missing; },
|
||||
}
|
||||
match Vec.push(&ctx.buffers, &absbuf as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&absbuf); ret .Missing; },
|
||||
}
|
||||
|
||||
var content = ByteBuffer.new();
|
||||
match read_all(abs, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Diag.err(diag, span_none(), .LexUnexpected, "@import: cannot open module");
|
||||
ByteBuffer.drop(&content);
|
||||
ret .Missing;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
var sidx: usize = 0;
|
||||
match set_add(ctx.sources, abs, text) {
|
||||
.Ok(index) => { sidx = index; },
|
||||
.Err(error) => { ByteBuffer.drop(&content); ret .Missing; },
|
||||
}
|
||||
match Vec.push(&ctx.buffers, &content as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&content); ret .Missing; },
|
||||
}
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => { Vec.drop(&tokens); ret .Missing; },
|
||||
}
|
||||
|
||||
var ast_val: Ast = undefined;
|
||||
match Ast.new(ctx.sources) {
|
||||
.Ok(v) => { ast_val = v; },
|
||||
.Err(error) => { Vec.drop(&tokens); ret .Missing; },
|
||||
}
|
||||
|
||||
var p = Parser.new(&ast_val, tokens, diag);
|
||||
p.file = sidx;
|
||||
Parser.parse(&p);
|
||||
Vec.drop(&tokens);
|
||||
if Diag.has_error(diag.*) || p.failed {
|
||||
Ast.drop(&ast_val);
|
||||
ret .Missing;
|
||||
}
|
||||
|
||||
var raw = alloc(@sizeof(Ast));
|
||||
if raw == null {
|
||||
Ast.drop(&ast_val);
|
||||
ret .Missing;
|
||||
}
|
||||
var ap = raw as *Ast;
|
||||
ap.* = ast_val;
|
||||
|
||||
var slot = mod_at(ctx, midx);
|
||||
if slot != null {
|
||||
slot.ast = ap;
|
||||
slot.ok = true;
|
||||
}
|
||||
ret .Ok(ap);
|
||||
}
|
||||
|
||||
fn loader_ctx_drop(ctx: *LoaderCtx) {
|
||||
var i: usize = 0;
|
||||
while i < ctx.mods.len {
|
||||
var m = mod_at(ctx, i);
|
||||
if m != null && m.ok && m.ast != null {
|
||||
Ast.drop(m.ast);
|
||||
free(m.ast as *any);
|
||||
m.ast = null;
|
||||
m.ok = false;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&ctx.mods);
|
||||
|
||||
var j: usize = 0;
|
||||
while j < ctx.buffers.len {
|
||||
match Vec.get(&ctx.buffers, j) {
|
||||
.Ok(ptr) => { ByteBuffer.drop(ptr as *ByteBuffer); },
|
||||
.Err(error) => {},
|
||||
}
|
||||
j = j + 1;
|
||||
}
|
||||
Vec.drop(&ctx.buffers);
|
||||
}
|
||||
|
||||
fn dump_sema(log: *Logger, path: ByteView): i32 {
|
||||
var content = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&content); }
|
||||
match read_all(path, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot read input file");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
match set_add(&sources, path, text) {
|
||||
.Ok(index) => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot register source");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var diag = Diag.new(&sources);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, &diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "lex failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Vec.drop(&tokens); }
|
||||
|
||||
var ast: Ast = undefined;
|
||||
match Ast.new(&sources) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "ast init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Ast.drop(&ast); }
|
||||
|
||||
var p = Parser.new(&ast, tokens, &diag);
|
||||
Parser.parse(&p);
|
||||
|
||||
if Diag.has_error(diag) || p.failed {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
var ctx = LoaderCtx {
|
||||
.sources = &sources,
|
||||
.mods = Vec.blank(@sizeof(ModEntry), @alignof(ModEntry)),
|
||||
.buffers = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
.diag = &diag,
|
||||
};
|
||||
defer { loader_ctx_drop(&ctx); }
|
||||
var loader = Loader { .load = driver_load, .user = &ctx as *any };
|
||||
|
||||
var sema: Sema = undefined;
|
||||
match Sema.new(&ast, &diag) {
|
||||
.Ok(v) => { sema = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "sema init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Sema.drop(&sema); }
|
||||
sema.sources = &sources;
|
||||
sema.loader = &loader;
|
||||
|
||||
Sema.run(&sema);
|
||||
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !Sema.dump(&sema, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping sema");
|
||||
ret 1;
|
||||
}
|
||||
if !Ast.dump(&ast, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping ast");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(out)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
type PipeMode = enum {
|
||||
Ir, // 只 dump IR
|
||||
Vm, // 继续 layout + ir2vm,dump VM
|
||||
Compile, // 继续 layout + ir2vm,写 outpath
|
||||
State, // 只逐步打日志(诊断“卡在哪一步”)
|
||||
C, // 继续 layout + ir2c,dump C
|
||||
}
|
||||
|
||||
fn step(mode: PipeMode, s: []u8) {
|
||||
if @enum2int(mode) == @enum2int(PipeMode.State) {
|
||||
match eprint(s) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
}
|
||||
|
||||
// 完整前端流水线:lex -> parse -> sema -> ast2ir ->(可选)layout + ir2vm。
|
||||
fn run_pipeline(log: *Logger, path: ByteView, mode: PipeMode, outpath: ByteView): i32 {
|
||||
var content = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&content); }
|
||||
match read_all(path, &content) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot read input file");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var text = ByteView.from_slice(ByteBuffer.as_slice(content));
|
||||
step(mode, "state: read ok\n");
|
||||
|
||||
var sources = set_new();
|
||||
defer { set_drop(&sources); }
|
||||
var main_idx: usize = 0;
|
||||
match set_add(&sources, path, text) {
|
||||
.Ok(index) => { main_idx = index; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "cannot register source");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
var diag = Diag.new(&sources);
|
||||
defer { Diag.drop(&diag); }
|
||||
|
||||
var tokens = Vec.blank(@sizeof(Token), @alignof(Token));
|
||||
match Lexer.lex(text, &diag, 0) {
|
||||
.Ok(v) => { tokens = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "lex failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Vec.drop(&tokens); }
|
||||
|
||||
var ast: Ast = undefined;
|
||||
match Ast.new(&sources) {
|
||||
.Ok(v) => { ast = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "ast init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Ast.drop(&ast); }
|
||||
|
||||
var p = Parser.new(&ast, tokens, &diag);
|
||||
p.file = main_idx;
|
||||
Parser.parse(&p);
|
||||
|
||||
if Diag.has_error(diag) || p.failed {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
step(mode, "state: parse ok\n");
|
||||
|
||||
var ctx = LoaderCtx {
|
||||
.sources = &sources,
|
||||
.mods = Vec.blank(@sizeof(ModEntry), @alignof(ModEntry)),
|
||||
.buffers = Vec.blank(@sizeof(ByteBuffer), @alignof(ByteBuffer)),
|
||||
.diag = &diag,
|
||||
};
|
||||
defer { loader_ctx_drop(&ctx); }
|
||||
var loader = Loader { .load = driver_load, .user = &ctx as *any };
|
||||
|
||||
var sema: Sema = undefined;
|
||||
match Sema.new(&ast, &diag) {
|
||||
.Ok(v) => { sema = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "sema init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { Sema.drop(&sema); }
|
||||
sema.sources = &sources;
|
||||
sema.loader = &loader;
|
||||
|
||||
Sema.run(&sema);
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
step(mode, "state: sema ok\n");
|
||||
|
||||
var builder = IrBuilder.builder_new();
|
||||
defer { IrBuilder.builder_drop(&builder); }
|
||||
var a2ir = Ast2Ir.new(&builder, &sema, &diag);
|
||||
defer { Ast2Ir.drop(&a2ir); }
|
||||
|
||||
var asts = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
defer { Vec.drop(&asts); }
|
||||
var ap: *Ast = *
|
||||
match Vec.push(&asts, &ap as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var mi: usize = 0;
|
||||
while mi < ctx.mods.len {
|
||||
var m = mod_at(&ctx, mi);
|
||||
if m != null && m.ok && m.ast != null {
|
||||
var mp: *Ast = m.ast;
|
||||
match Vec.push(&asts, &mp as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
}
|
||||
mi = mi + 1;
|
||||
}
|
||||
|
||||
Ast2Ir.run(&a2ir, asts);
|
||||
if Diag.has_error(diag) {
|
||||
var rendered = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&rendered); }
|
||||
match Diag.render_human(&diag, &rendered) { .Ok => {}, .Err(error) => {} }
|
||||
eprint(ByteBuffer.as_slice(rendered));
|
||||
ret 1;
|
||||
}
|
||||
|
||||
step(mode, "state: ast2ir ok\n");
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.Ir) {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !Ir.dump(&builder.ir, &sema.types.ir, &sema.attr, &out) {
|
||||
Logger.fatal(log, "out of memory while dumping ir");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(out)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 类型就绪后解析布局,再做 IR -> VM。
|
||||
var layout: Layout = undefined;
|
||||
match LayoutM.new() {
|
||||
.Ok(v) => { layout = v; },
|
||||
.Err(error) => {
|
||||
Logger.error(log, "layout init failed");
|
||||
ret 1;
|
||||
},
|
||||
}
|
||||
defer { LayoutM.drop(&layout); }
|
||||
LayoutM.resolve(&layout, &sema.types.ir);
|
||||
step(mode, "state: layout ok\n");
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.C) {
|
||||
var cout = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&cout); }
|
||||
var ok = Ir2CM.compile(&builder.ir, &sema.types.ir, &layout, &sema.attr, &cout);
|
||||
if outpath.len != 0 {
|
||||
var f = open_cstr(outpath, ByteView.from_slice("wb"));
|
||||
if !File.valid(&f) {
|
||||
Logger.error(log, "cannot open C output file");
|
||||
ret 1;
|
||||
}
|
||||
match write_all(&f, ByteBuffer.as_slice(cout)) { .Ok => {}, .Err(error) => {} }
|
||||
ignore_i32(File.close(&f));
|
||||
} else {
|
||||
match print(ByteBuffer.as_slice(cout)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
Logger.error(log, "ir2c failed (see generated C for unsupported markers)");
|
||||
ret 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
var ir2vm: Ir2Vm = undefined;
|
||||
Ir2VmM.init(&ir2vm, &builder.ir, &sema.types.ir, &layout, &sema.attr);
|
||||
defer { Ir2VmM.drop(&ir2vm); }
|
||||
ignore_i32(Ir2VmM.run(&ir2vm));
|
||||
step(mode, "state: ir2vm ok\n");
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.Vm) {
|
||||
var vout = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&vout); }
|
||||
if !Ir2VmM.dump(&ir2vm, &vout) {
|
||||
Logger.fatal(log, "out of memory while dumping vm");
|
||||
ret 1;
|
||||
}
|
||||
match print(ByteBuffer.as_slice(vout)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 1; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
if @enum2int(mode) == @enum2int(PipeMode.State) {
|
||||
ret 0;
|
||||
}
|
||||
|
||||
if !Ir2VmM.store(&ir2vm, outpath) {
|
||||
Logger.error(log, "cannot write output file");
|
||||
ret 1;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn ignore_i32(v: i32) {}
|
||||
|
||||
fn main(argc: i32, argv: **u8): i32 {
|
||||
// 无参数:自测运行(`nob test stage2`)视为成功。
|
||||
if argc <= 1 { ret 0; }
|
||||
|
||||
var log = Logger.new(ByteView.from_slice("splc1"));
|
||||
|
||||
var arg1 = ByteView.from_cstr(argv[1]);
|
||||
|
||||
if !ByteView.eq(arg1, ByteView.from_slice("--dump")) {
|
||||
// 编译模式:splc1 <in> <out>
|
||||
if argc < 3 {
|
||||
Logger.fatal(&log, "usage: splc1 <in> <out> | splc1 --dump <lex|ast|sema|ir|vm> <file>");
|
||||
ret 1;
|
||||
}
|
||||
var in = ByteView.from_cstr(argv[1]);
|
||||
var out = ByteView.from_cstr(argv[2]);
|
||||
ret run_pipeline(&log, in, .Compile, out);
|
||||
}
|
||||
|
||||
if argc < 4 {
|
||||
Logger.fatal(&log, "usage: splc1 --dump <lex|ast|sema|ir|vm|c> <file> [out]");
|
||||
ret 1;
|
||||
}
|
||||
var mode = ByteView.from_cstr(argv[2]);
|
||||
var path = ByteView.from_cstr(argv[3]);
|
||||
var dout = ByteView.empty();
|
||||
if argc >= 5 { dout = ByteView.from_cstr(argv[4]); }
|
||||
|
||||
if ByteView.eq(mode, ByteView.from_slice("lex")) {
|
||||
ret dump_lex(&log, path);
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("ast")) {
|
||||
ret dump_ast(&log, path);
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("sema")) {
|
||||
ret dump_sema(&log, path);
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("ir")) {
|
||||
ret run_pipeline(&log, path, .Ir, ByteView.empty());
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("vm")) {
|
||||
ret run_pipeline(&log, path, .Vm, ByteView.empty());
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("state")) {
|
||||
ret run_pipeline(&log, path, .State, ByteView.empty());
|
||||
}
|
||||
if ByteView.eq(mode, ByteView.from_slice("c")) {
|
||||
ret run_pipeline(&log, path, .C, dout);
|
||||
}
|
||||
Logger.fatal(&log, "dump mode not implemented");
|
||||
ret 1;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Expect retcode = 6
|
||||
|
||||
// exit.spl
|
||||
// usage: type exit = @import("exit.spl"); exit.ok();
|
||||
|
||||
#extern(vm) fn vm_exit(code: i32);
|
||||
|
||||
fn now(code: i32) {
|
||||
vm_exit(code);
|
||||
}
|
||||
|
||||
fn ok() {
|
||||
vm_exit(0);
|
||||
}
|
||||
|
||||
fn fail() {
|
||||
vm_exit(1);
|
||||
}
|
||||
|
||||
fn main(): void {
|
||||
now(6);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#extern(vm) fn vm_fopen(path: *any, mode: *any): *any;
|
||||
#extern(vm) fn vm_fclose(f: *any): i32;
|
||||
#extern(vm) fn vm_fread(buf: *any, sz: usize, n: usize, f: *any): usize;
|
||||
#extern(vm) fn vm_fwrite(buf: *any, sz: usize, n: usize, f: *any): usize;
|
||||
#extern(vm) fn vm_fsize(f: *any): usize;
|
||||
#extern(vm) fn vm_stdin(): *any;
|
||||
#extern(vm) fn vm_stdout(): *any;
|
||||
#extern(vm) fn vm_stderr(): *any;
|
||||
#extern(vm) fn vm_putchar(c: i32): i32;
|
||||
#extern(vm) fn vm_getchar(): i32;
|
||||
|
||||
handle: *any,
|
||||
type File = @self;
|
||||
|
||||
fn stdin(): File { ret File { .handle = vm_stdin() }; }
|
||||
fn stdout(): File { ret File { .handle = vm_stdout() }; }
|
||||
fn stderr(): File { ret File { .handle = vm_stderr() }; }
|
||||
|
||||
fn open(path: *u8, mode: *u8): File {
|
||||
ret File { .handle = vm_fopen(path as *any, mode as *any) };
|
||||
}
|
||||
|
||||
fn valid(f: *File): bool {
|
||||
ret f.handle != null;
|
||||
}
|
||||
|
||||
fn write(f: *File, data: *u8, n: u64): u64 {
|
||||
if f.handle == null { ret 0; }
|
||||
ret vm_fwrite(data as *any, 1, n, f.handle);
|
||||
}
|
||||
|
||||
fn write_byte(f: *File, b: u8): u64 {
|
||||
var tmp: u8 = b;
|
||||
ret write(f, &tmp, 1);
|
||||
}
|
||||
|
||||
fn read(f: *File, buf: *u8, n: usize): usize {
|
||||
if f.handle == null { ret 0; }
|
||||
ret vm_fread(buf as *any, 1, n, f.handle);
|
||||
}
|
||||
|
||||
fn size(f: *File): usize {
|
||||
if f.handle == null { ret 0; }
|
||||
ret vm_fsize(f.handle);
|
||||
}
|
||||
|
||||
fn close(f: *File): i32 {
|
||||
if f.handle == null { ret -1; }
|
||||
var r = vm_fclose(f.handle);
|
||||
f.handle = null;
|
||||
ret r;
|
||||
}
|
||||
|
||||
fn putchar(c: u8) {
|
||||
vm_putchar(c as i32);
|
||||
}
|
||||
|
||||
fn getchar(): i32 {
|
||||
ret vm_getchar();
|
||||
}
|
||||
|
||||
fn main(): void {
|
||||
var out = stdout();
|
||||
@assert(out.handle != null, "stdout");
|
||||
var msg = "file: ok\n";
|
||||
var n = write(&out, msg.ptr, msg.len);
|
||||
@assert(n == msg.len, "write");
|
||||
putchar(46 as u8);
|
||||
putchar(10 as u8);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#extern(vm) fn vm_alloc(sz: usize): *any;
|
||||
#extern(vm) fn vm_free(p: *any);
|
||||
#extern(vm) fn vm_realloc(p: *any, sz: usize): *any;
|
||||
#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any;
|
||||
#extern(vm) fn vm_memcmp(a: *any, b: *any, n: usize): isize;
|
||||
|
||||
fn alloc(sz: usize): *any {
|
||||
if sz == 0 { ret null; }
|
||||
ret vm_alloc(sz);
|
||||
}
|
||||
|
||||
fn free(p: *any) {
|
||||
if p == null { ret; }
|
||||
vm_free(p);
|
||||
}
|
||||
|
||||
fn realloc(p: *any, sz: usize): *any {
|
||||
if p == null { ret alloc(sz); }
|
||||
if sz == 0 { free(p); ret null; }
|
||||
ret vm_realloc(p, sz);
|
||||
}
|
||||
|
||||
fn copy(dst: *any, src: *any, n: usize): *any {
|
||||
if n == 0 { ret dst; }
|
||||
ret vm_memcpy(dst, src, n);
|
||||
}
|
||||
|
||||
fn cmp(a: *any, b: *any, n: usize): isize {
|
||||
if n == 0 { ret 0; }
|
||||
ret vm_memcmp(a, b, n);
|
||||
}
|
||||
|
||||
fn set(dst: *any, byte: u8, n: usize): *any {
|
||||
var d = dst as *u8;
|
||||
var i: usize = 0;
|
||||
while i < n {
|
||||
d[i] = byte;
|
||||
i = i + 1;
|
||||
}
|
||||
ret dst;
|
||||
}
|
||||
|
||||
fn zero(dst: *any, n: usize): *any {
|
||||
ret set(dst, 0 as u8, n);
|
||||
}
|
||||
|
||||
fn main(): void {
|
||||
var p = alloc(16);
|
||||
@assert(p != null, "alloc");
|
||||
var q = alloc(16);
|
||||
@assert(p != q, "distinct");
|
||||
@assert(alloc(0) == null, "alloc(0)");
|
||||
|
||||
var a: u8 = 42;
|
||||
var b: u8 = 0;
|
||||
copy(&b as *any, &a as *any, 1);
|
||||
@assert(b == 42, "copy");
|
||||
|
||||
var x: u8 = 1;
|
||||
var y: u8 = 2;
|
||||
@assert(cmp(&x as *any, &y as *any, 1) < 0, "cmp lt");
|
||||
@assert(cmp(&y as *any, &x as *any, 1) > 0, "cmp gt");
|
||||
@assert(cmp(&x as *any, &x as *any, 1) == 0, "cmp eq");
|
||||
|
||||
var buf = alloc(8);
|
||||
zero(buf, 8);
|
||||
var bp = buf as *u8;
|
||||
@assert(bp[0] == 0, "zero");
|
||||
@assert(bp[7] == 0, "zero end");
|
||||
set(buf, 0xAB as u8, 8);
|
||||
@assert(bp[0] == 0xAB as u8, "set");
|
||||
|
||||
var r = realloc(p, 32);
|
||||
@assert(r != null, "realloc");
|
||||
free(r);
|
||||
free(q);
|
||||
free(buf);
|
||||
free(null);
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
/* spl_rt.h - SPL native runtime (header-only, C89-friendly)
|
||||
*
|
||||
* 提供 ir2c 生成代码所需的所有 `#extern(vm)` 符号,以及 SPL 基础类型别名。
|
||||
* 目标:生成代码可用 C89 语法通过(不依赖 stdint/stdbool)。
|
||||
*/
|
||||
|
||||
#ifndef __SPL_RT_H__
|
||||
#define __SPL_RT_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
/* 语义清晰的 inline:C99 用 static inline,GNU 用 __inline__,C89 退化为 static */
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||
#define SPL_INLINE static inline
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define SPL_INLINE static __inline__
|
||||
#else
|
||||
#define SPL_INLINE static
|
||||
#endif
|
||||
|
||||
/* ---- SPL 基础类型别名 ---- */
|
||||
typedef unsigned char u8;
|
||||
typedef signed char i8;
|
||||
typedef unsigned short u16;
|
||||
typedef signed short i16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed int i32;
|
||||
typedef unsigned long long u64;
|
||||
typedef signed long long i64;
|
||||
typedef u64 usize;
|
||||
typedef i64 isize;
|
||||
/* 注:不定义 bool/true/false,避免与 C23 关键字冲突;生成代码用 u8 表示布尔。 */
|
||||
|
||||
/* ---- panic / trap ---- */
|
||||
SPL_INLINE void spl_panic(const char *msg) {
|
||||
fprintf(stderr, "%s\n", msg ? msg : "panic");
|
||||
exit(1);
|
||||
}
|
||||
SPL_INLINE void spl_trap(void) { abort(); }
|
||||
|
||||
/* ---- mem ---- */
|
||||
SPL_INLINE void *vm_alloc(u64 sz) {
|
||||
if (sz == 0)
|
||||
return NULL;
|
||||
return malloc((size_t)sz);
|
||||
}
|
||||
SPL_INLINE void vm_free(void *p) {
|
||||
if (p)
|
||||
free(p);
|
||||
}
|
||||
SPL_INLINE void *vm_realloc(void *p, u64 sz) {
|
||||
if (!p)
|
||||
return vm_alloc(sz);
|
||||
if (sz == 0) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
return realloc(p, (size_t)sz);
|
||||
}
|
||||
SPL_INLINE void *vm_memcpy(void *dst, const void *src, u64 n) {
|
||||
if (n)
|
||||
memcpy(dst, src, (size_t)n);
|
||||
return dst;
|
||||
}
|
||||
SPL_INLINE void *vm_memmove(void *dst, const void *src, u64 n) {
|
||||
if (n)
|
||||
memmove(dst, src, (size_t)n);
|
||||
return dst;
|
||||
}
|
||||
SPL_INLINE i64 vm_memcmp(const void *a, const void *b, u64 n) {
|
||||
if (n == 0)
|
||||
return 0;
|
||||
return (i64)memcmp(a, b, (size_t)n);
|
||||
}
|
||||
SPL_INLINE i32 vm_bytes_eq(const void *a, u64 alen, const void *b, u64 blen) {
|
||||
if (alen != blen)
|
||||
return 0;
|
||||
if (alen == 0)
|
||||
return 1;
|
||||
if (!a || !b)
|
||||
return 0;
|
||||
return memcmp(a, b, (size_t)alen) == 0;
|
||||
}
|
||||
|
||||
/* ---- Vec ---- */
|
||||
typedef struct {
|
||||
usize raw;
|
||||
usize data;
|
||||
usize len;
|
||||
usize cap;
|
||||
usize elem_size;
|
||||
usize elem_align;
|
||||
} spl_vec_t;
|
||||
|
||||
SPL_INLINE int spl_vec_is_valid(const spl_vec_t *v) {
|
||||
usize a;
|
||||
if (v->elem_size == 0)
|
||||
return 0;
|
||||
a = v->elem_align;
|
||||
if (a == 0 || (a & (a - 1)) != 0)
|
||||
return 0;
|
||||
if (v->len > v->cap)
|
||||
return 0;
|
||||
if (v->cap == 0)
|
||||
return v->raw == 0 && v->data == 0 && v->len == 0;
|
||||
if (v->raw == 0 || v->data == 0)
|
||||
return 0;
|
||||
return (v->data & (a - 1)) == 0;
|
||||
}
|
||||
|
||||
SPL_INLINE int spl_align_ok(usize a) { return a != 0 && (a & (a - 1)) == 0; }
|
||||
|
||||
SPL_INLINE void spl_grow(usize current, usize need, usize *out) {
|
||||
usize cap = current < 8 ? 8 : current;
|
||||
usize limit = ~(usize)0;
|
||||
while (cap < need) {
|
||||
if (cap > limit / 2) {
|
||||
cap = need;
|
||||
break;
|
||||
}
|
||||
cap = cap * 2;
|
||||
}
|
||||
*out = cap;
|
||||
}
|
||||
|
||||
SPL_INLINE int spl_vec_allocate(spl_vec_t *out, usize capacity, usize elem_size, usize elem_align) {
|
||||
usize bytes, mask, addr;
|
||||
void *raw;
|
||||
if (elem_size == 0)
|
||||
return 1;
|
||||
if (!spl_align_ok(elem_align))
|
||||
return 4;
|
||||
out->raw = 0;
|
||||
out->data = 0;
|
||||
out->len = 0;
|
||||
out->cap = 0;
|
||||
out->elem_size = elem_size;
|
||||
out->elem_align = elem_align;
|
||||
if (capacity == 0)
|
||||
return 0;
|
||||
if (capacity > ~(usize)0 / elem_size)
|
||||
return 3;
|
||||
bytes = capacity * elem_size;
|
||||
mask = elem_align - 1;
|
||||
if (bytes > ~(usize)0 - mask)
|
||||
return 3;
|
||||
raw = malloc(bytes + mask);
|
||||
if (!raw)
|
||||
return 3;
|
||||
addr = (usize)raw;
|
||||
if (addr > ~(usize)0 - mask) {
|
||||
free(raw);
|
||||
return 3;
|
||||
}
|
||||
out->raw = addr;
|
||||
out->data = (addr + mask) & ~mask;
|
||||
out->cap = capacity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SPL_INLINE int spl_vec_reserve_raw(spl_vec_t *v, usize need) {
|
||||
usize new_cap = 0, used = 0, old;
|
||||
spl_vec_t fresh;
|
||||
int rc;
|
||||
if (need <= v->cap)
|
||||
return 0;
|
||||
spl_grow(v->cap, need, &new_cap);
|
||||
rc = spl_vec_allocate(&fresh, new_cap, v->elem_size, v->elem_align);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (v->len != 0) {
|
||||
if (v->len > ~(usize)0 / v->elem_size) {
|
||||
free((void *)fresh.raw);
|
||||
return 3;
|
||||
}
|
||||
used = v->len * v->elem_size;
|
||||
}
|
||||
if (used)
|
||||
memcpy((void *)fresh.data, (void *)v->data, used);
|
||||
fresh.len = v->len;
|
||||
old = v->raw;
|
||||
*v = fresh;
|
||||
if (old)
|
||||
free((void *)old);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SPL_INLINE void spl_vec_release(spl_vec_t *v) {
|
||||
usize es = v->elem_size, ea = v->elem_align;
|
||||
if (v->raw)
|
||||
free((void *)v->raw);
|
||||
v->raw = 0;
|
||||
v->data = 0;
|
||||
v->len = 0;
|
||||
v->cap = 0;
|
||||
v->elem_size = es;
|
||||
v->elem_align = ea;
|
||||
}
|
||||
|
||||
SPL_INLINE i32 vm_vec_valid(void *vv) {
|
||||
const spl_vec_t *v = (const spl_vec_t *)vv;
|
||||
return v ? spl_vec_is_valid(v) : 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_push_raw(void *vv, const void *elem) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v || !elem)
|
||||
return 0;
|
||||
if (v->len < v->cap) {
|
||||
memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_get(void *vv, u64 index, void **out) {
|
||||
const spl_vec_t *v = (const spl_vec_t *)vv;
|
||||
if (!v || !out)
|
||||
return 1;
|
||||
if (!spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (index >= v->len)
|
||||
return 2;
|
||||
*out = (void *)(v->data + index * v->elem_size);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_push(void *vv, const void *elem) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
int rc;
|
||||
if (!v || !elem || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (v->len < v->cap) {
|
||||
memcpy((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 0;
|
||||
}
|
||||
rc = spl_vec_reserve_raw(v, v->len + 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
memmove((void *)(v->data + v->len * v->elem_size), elem, v->elem_size);
|
||||
v->len++;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_allocate(void *out, u64 capacity, u64 elem_size, u64 elem_align) {
|
||||
return spl_vec_allocate((spl_vec_t *)out, capacity, elem_size, elem_align);
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_reserve_raw(void *vv, u64 need) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v)
|
||||
return 1;
|
||||
return spl_vec_reserve_raw(v, need);
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_append(void *vv, const void *elems, u64 count) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
usize copy_bytes, new_len, used, source_offset = 0;
|
||||
int source_inside = 0, rc;
|
||||
if (!v || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
if (!elems)
|
||||
return 1;
|
||||
if (count > ~(usize)0 / v->elem_size)
|
||||
return 3;
|
||||
copy_bytes = count * v->elem_size;
|
||||
if (v->len > ~(usize)0 - count)
|
||||
return 3;
|
||||
new_len = v->len + count;
|
||||
used = v->len * v->elem_size;
|
||||
if (used) {
|
||||
usize base = v->data;
|
||||
usize source = (usize)elems;
|
||||
if (source >= base && source - base < used) {
|
||||
source_offset = source - base;
|
||||
if (copy_bytes > used - source_offset)
|
||||
return 2;
|
||||
source_inside = 1;
|
||||
}
|
||||
}
|
||||
rc = spl_vec_reserve_raw(v, new_len);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (source_inside)
|
||||
elems = (const void *)(v->data + source_offset);
|
||||
memmove((void *)(v->data + used), elems, copy_bytes);
|
||||
v->len = new_len;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_set(void *vv, u64 index, const void *elem) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v || !elem || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (index >= v->len)
|
||||
return 2;
|
||||
memmove((void *)(v->data + index * v->elem_size), elem, v->elem_size);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_pop(void *vv, void *out) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (!v || !out || !spl_vec_is_valid(v))
|
||||
return 1;
|
||||
if (v->len == 0)
|
||||
return 2;
|
||||
memmove(out, (void *)(v->data + (v->len - 1) * v->elem_size), v->elem_size);
|
||||
v->len--;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_vec_clone(void *outp, const void *srcp) {
|
||||
spl_vec_t *out = (spl_vec_t *)outp;
|
||||
const spl_vec_t *src = (const spl_vec_t *)srcp;
|
||||
spl_vec_t fresh;
|
||||
usize bytes;
|
||||
int rc;
|
||||
if (!out || !src || !spl_vec_is_valid(out) || !spl_vec_is_valid(src))
|
||||
return 1;
|
||||
if (out->elem_size != src->elem_size || out->elem_align != src->elem_align)
|
||||
return 1;
|
||||
fresh.raw = 0;
|
||||
fresh.data = 0;
|
||||
fresh.len = 0;
|
||||
fresh.cap = 0;
|
||||
fresh.elem_size = src->elem_size;
|
||||
fresh.elem_align = src->elem_align;
|
||||
rc = spl_vec_reserve_raw(&fresh, src->len);
|
||||
if (rc)
|
||||
return rc;
|
||||
bytes = src->len * src->elem_size;
|
||||
if (bytes)
|
||||
memcpy((void *)fresh.data, (void *)src->data, bytes);
|
||||
fresh.len = src->len;
|
||||
if (out->raw)
|
||||
free((void *)out->raw);
|
||||
*out = fresh;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE void vm_vec_drop(void *vv) {
|
||||
spl_vec_t *v = (spl_vec_t *)vv;
|
||||
if (v)
|
||||
spl_vec_release(v);
|
||||
}
|
||||
|
||||
/* ---- Map ---- */
|
||||
typedef struct {
|
||||
spl_vec_t keys;
|
||||
spl_vec_t vals;
|
||||
spl_vec_t states;
|
||||
usize size;
|
||||
usize key_size;
|
||||
usize val_size;
|
||||
usize hash_kind;
|
||||
} spl_map_t;
|
||||
|
||||
SPL_INLINE usize spl_hash_usize_value(usize value) {
|
||||
usize prime = 1099511628211ull;
|
||||
usize h = value;
|
||||
h = (h ^ (h >> 30)) * prime;
|
||||
h = h ^ (h >> 27);
|
||||
h = h * prime;
|
||||
h = h ^ (h >> 31);
|
||||
if (h == 0)
|
||||
return prime;
|
||||
return h;
|
||||
}
|
||||
SPL_INLINE usize spl_hash_bytes(const void *data, usize len) {
|
||||
const unsigned char *p = (const unsigned char *)data;
|
||||
usize h = 14695981039346656037ull;
|
||||
usize prime = 1099511628211ull;
|
||||
usize i;
|
||||
for (i = 0; i < len; i++)
|
||||
h = (h ^ (usize)p[i]) * prime;
|
||||
return h;
|
||||
}
|
||||
SPL_INLINE usize spl_map_hash(const spl_map_t *m, const void *key) {
|
||||
const usize *v;
|
||||
if (m->hash_kind == 0)
|
||||
return spl_hash_usize_value(*(const usize *)key);
|
||||
if (m->hash_kind == 2)
|
||||
return 3;
|
||||
v = (const usize *)key;
|
||||
return spl_hash_bytes((const void *)v[0], v[1]);
|
||||
}
|
||||
SPL_INLINE int spl_map_eq(const spl_map_t *m, const void *a, const void *b) {
|
||||
const usize *x;
|
||||
const usize *y;
|
||||
if (m->hash_kind == 0 || m->hash_kind == 2)
|
||||
return *(const usize *)a == *(const usize *)b;
|
||||
x = (const usize *)a;
|
||||
y = (const usize *)b;
|
||||
if (x[1] != y[1])
|
||||
return 0;
|
||||
if (x[1] == 0)
|
||||
return 1;
|
||||
return memcmp((const void *)x[0], (const void *)y[0], x[1]) == 0;
|
||||
}
|
||||
SPL_INLINE int spl_map_valid(const spl_map_t *m) {
|
||||
if (m->key_size == 0 || m->val_size == 0)
|
||||
return 0;
|
||||
if (m->keys.len != m->states.len || m->vals.len != m->states.len)
|
||||
return 0;
|
||||
if (m->size > m->states.len)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
SPL_INLINE usize spl_map_find_slot(const spl_map_t *m, const void *key) {
|
||||
usize mask, i, probes;
|
||||
unsigned char *st;
|
||||
if (m->states.cap == 0)
|
||||
return ~(usize)0;
|
||||
mask = m->states.cap - 1;
|
||||
i = spl_map_hash(m, key) & mask;
|
||||
st = (unsigned char *)m->states.data;
|
||||
probes = 0;
|
||||
while (probes < m->states.cap && st[i] != 0) {
|
||||
if (st[i] == 1 && spl_map_eq(m, (const void *)(m->keys.data + i * m->key_size), key))
|
||||
return i;
|
||||
i = (i + 1) & mask;
|
||||
probes++;
|
||||
}
|
||||
return ~(usize)0;
|
||||
}
|
||||
SPL_INLINE void spl_map_place(spl_map_t *m, const void *key, const void *val) {
|
||||
usize mask, i, first_deleted, probes;
|
||||
unsigned char *st;
|
||||
mask = m->states.cap - 1;
|
||||
i = spl_map_hash(m, key) & mask;
|
||||
st = (unsigned char *)m->states.data;
|
||||
first_deleted = ~(usize)0;
|
||||
probes = 0;
|
||||
while (probes < m->states.cap && st[i] != 0) {
|
||||
if (st[i] == 2 && first_deleted == ~(usize)0)
|
||||
first_deleted = i;
|
||||
i = (i + 1) & mask;
|
||||
probes++;
|
||||
}
|
||||
if (first_deleted != ~(usize)0)
|
||||
i = first_deleted;
|
||||
memcpy((void *)(m->keys.data + i * m->key_size), key, m->key_size);
|
||||
memcpy((void *)(m->vals.data + i * m->val_size), val, m->val_size);
|
||||
st[i] = 1;
|
||||
m->size++;
|
||||
}
|
||||
SPL_INLINE int spl_map_reserve(spl_map_t *m, usize need) {
|
||||
usize new_cap = 0, i;
|
||||
spl_map_t fresh;
|
||||
int rc;
|
||||
if (!spl_map_valid(m))
|
||||
return 1;
|
||||
if (need <= m->states.cap)
|
||||
return 0;
|
||||
spl_grow(m->states.cap, need, &new_cap);
|
||||
fresh = *m;
|
||||
fresh.keys.raw = 0;
|
||||
fresh.keys.data = 0;
|
||||
fresh.keys.len = 0;
|
||||
fresh.keys.cap = 0;
|
||||
fresh.vals.raw = 0;
|
||||
fresh.vals.data = 0;
|
||||
fresh.vals.len = 0;
|
||||
fresh.vals.cap = 0;
|
||||
fresh.states.raw = 0;
|
||||
fresh.states.data = 0;
|
||||
fresh.states.len = 0;
|
||||
fresh.states.cap = 0;
|
||||
fresh.size = 0;
|
||||
rc = spl_vec_reserve_raw(&fresh.keys, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
rc = spl_vec_reserve_raw(&fresh.vals, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
rc = spl_vec_reserve_raw(&fresh.states, new_cap);
|
||||
if (rc)
|
||||
goto fail;
|
||||
fresh.keys.len = new_cap;
|
||||
fresh.vals.len = new_cap;
|
||||
fresh.states.len = new_cap;
|
||||
memset((void *)fresh.states.data, 0, new_cap);
|
||||
for (i = 0; i < m->states.len; i++) {
|
||||
if (((unsigned char *)m->states.data)[i] == 1)
|
||||
spl_map_place(&fresh, (const void *)(m->keys.data + i * m->key_size),
|
||||
(const void *)(m->vals.data + i * m->val_size));
|
||||
}
|
||||
spl_vec_release(&m->keys);
|
||||
spl_vec_release(&m->vals);
|
||||
spl_vec_release(&m->states);
|
||||
*m = fresh;
|
||||
return 0;
|
||||
fail:
|
||||
spl_vec_release(&fresh.keys);
|
||||
spl_vec_release(&fresh.vals);
|
||||
spl_vec_release(&fresh.states);
|
||||
return rc;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_valid(void *mm) {
|
||||
const spl_map_t *m = (const spl_map_t *)mm;
|
||||
return m ? spl_map_valid(m) : 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_reserve(void *mm, u64 need) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
if (!m)
|
||||
return 1;
|
||||
return spl_map_reserve(m, need);
|
||||
}
|
||||
SPL_INLINE i32 vm_map_put(void *mm, const void *key, const void *val) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
usize existing;
|
||||
if (!m || !key || !val || !spl_map_valid(m))
|
||||
return 1;
|
||||
existing = spl_map_find_slot(m, key);
|
||||
if (existing != ~(usize)0) {
|
||||
memcpy((void *)(m->vals.data + existing * m->val_size), val, m->val_size);
|
||||
return 0;
|
||||
}
|
||||
if (m->states.cap == 0) {
|
||||
int rc = spl_map_reserve(m, 8);
|
||||
if (rc)
|
||||
return rc;
|
||||
} else {
|
||||
usize next_size, scaled;
|
||||
if (m->size > ~(usize)0 - 1)
|
||||
return 3;
|
||||
next_size = m->size + 1;
|
||||
if (next_size > ~(usize)0 / 128)
|
||||
return 3;
|
||||
scaled = next_size * 128;
|
||||
if (scaled / m->states.cap >= 70) {
|
||||
int rc;
|
||||
if (m->states.cap > ~(usize)0 / 2)
|
||||
return 3;
|
||||
rc = spl_map_reserve(m, m->states.cap * 2);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
spl_map_place(m, key, val);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_get(void *mm, const void *key, void *out) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
usize i;
|
||||
if (!m || !key || !out || !spl_map_valid(m))
|
||||
return 1;
|
||||
i = spl_map_find_slot(m, key);
|
||||
if (i == ~(usize)0)
|
||||
return 1;
|
||||
memcpy(out, (void *)(m->vals.data + i * m->val_size), m->val_size);
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE i32 vm_map_del(void *mm, const void *key) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
usize i;
|
||||
if (!m || !key || !spl_map_valid(m))
|
||||
return 1;
|
||||
i = spl_map_find_slot(m, key);
|
||||
if (i == ~(usize)0)
|
||||
return 1;
|
||||
((unsigned char *)m->states.data)[i] = 2;
|
||||
m->size--;
|
||||
return 0;
|
||||
}
|
||||
SPL_INLINE void vm_map_clear(void *mm) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
if (!m)
|
||||
return;
|
||||
if (m->states.data && m->states.len)
|
||||
memset((void *)m->states.data, 0, m->states.len);
|
||||
m->size = 0;
|
||||
}
|
||||
SPL_INLINE void vm_map_drop(void *mm) {
|
||||
spl_map_t *m = (spl_map_t *)mm;
|
||||
if (!m)
|
||||
return;
|
||||
spl_vec_release(&m->keys);
|
||||
spl_vec_release(&m->vals);
|
||||
spl_vec_release(&m->states);
|
||||
m->size = 0;
|
||||
}
|
||||
|
||||
/* ---- file ---- */
|
||||
SPL_INLINE void *vm_fopen(void *path, void *mode) {
|
||||
return (void *)fopen((const char *)path, (const char *)mode);
|
||||
}
|
||||
SPL_INLINE i32 vm_fclose(void *f) { return f ? fclose((FILE *)f) : 0; }
|
||||
SPL_INLINE u64 vm_fread(void *buf, u64 sz, u64 n, void *f) {
|
||||
return f ? (u64)fread(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0;
|
||||
}
|
||||
SPL_INLINE u64 vm_fwrite(void *buf, u64 sz, u64 n, void *f) {
|
||||
return f ? (u64)fwrite(buf, (size_t)sz, (size_t)n, (FILE *)f) : 0;
|
||||
}
|
||||
SPL_INLINE u64 vm_fsize(void *f) {
|
||||
long cur, end;
|
||||
if (!f)
|
||||
return 0;
|
||||
cur = ftell((FILE *)f);
|
||||
if (cur < 0)
|
||||
return 0;
|
||||
if (fseek((FILE *)f, 0, SEEK_END) != 0)
|
||||
return 0;
|
||||
end = ftell((FILE *)f);
|
||||
fseek((FILE *)f, cur, SEEK_SET);
|
||||
return end < 0 ? 0 : (u64)end;
|
||||
}
|
||||
SPL_INLINE void *vm_stdin(void) { return (void *)stdin; }
|
||||
SPL_INLINE void *vm_stdout(void) { return (void *)stdout; }
|
||||
SPL_INLINE void *vm_stderr(void) { return (void *)stderr; }
|
||||
SPL_INLINE i32 vm_putchar(i32 c) { return fputc(c, stdout); }
|
||||
SPL_INLINE i32 vm_getchar(void) { return fgetc(stdin); }
|
||||
|
||||
/* ---- misc ---- */
|
||||
SPL_INLINE void vm_exit(i32 code) { exit(code); }
|
||||
SPL_INLINE u64 vm_timestamp(u64 kind) {
|
||||
time_t sec = time(NULL);
|
||||
if (kind == 1)
|
||||
return (u64)sec;
|
||||
if (kind == 2)
|
||||
return 0;
|
||||
if (kind == 4)
|
||||
return (u64)sec * 1000000000ull;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 内嵌 VM(comptime):实现在 spl_vm_embed.c,链接 stage0 VM ---- */
|
||||
#define SPL_VM_EMBED_IMPLIMENTATION
|
||||
#include "spl_vm_embed.h"
|
||||
|
||||
#endif /* __SPL_RT_H__ */
|
||||
@@ -0,0 +1,109 @@
|
||||
/* spl_vm_embed.h - 原生编译器内嵌 VM(comptime 执行)。
|
||||
*
|
||||
* 单 TU 合并式:定义 SPL_VM_EMBED_IMPLIMENTATION 后 include 本头,会把 stage0 的
|
||||
* spl_mcode.c / spl_syscall.c / spl_vm.c 一并纳入当前翻译单元(不单独编译/链接)。
|
||||
*/
|
||||
|
||||
#ifndef __SPL_VM_EMBED_H__
|
||||
#define __SPL_VM_EMBED_H__
|
||||
|
||||
void *vm_new(void);
|
||||
void vm_drop(void *vm);
|
||||
int vm_load_from_buffer(void *vm, const void *buf, unsigned long long len);
|
||||
void vm_push(void *vm, unsigned long long val);
|
||||
int vm_call(void *vm, const char *name, unsigned long long nargs);
|
||||
int vm_run(void *vm);
|
||||
|
||||
#ifdef SPL_VM_EMBED_IMPLIMENTATION
|
||||
|
||||
#include "../../stage0/spl_mcode.h"
|
||||
void spl_syscall_register(spl_prog_t *prog) { (void)prog; /* stub */ }
|
||||
|
||||
#include "../../stage0/spl_mcode.c"
|
||||
#include "../../stage0/spl_vm.c"
|
||||
|
||||
void *vm_new(void) {
|
||||
spl_vm_t *vm = (spl_vm_t *)malloc(sizeof(spl_vm_t));
|
||||
if (!vm)
|
||||
return 0;
|
||||
spl_vm_init(vm);
|
||||
return vm;
|
||||
}
|
||||
|
||||
void vm_drop(void *p) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
if (!vm)
|
||||
return;
|
||||
if (vm->prog) {
|
||||
spl_prog_drop(vm->prog);
|
||||
free(vm->prog);
|
||||
vm->prog = 0;
|
||||
}
|
||||
spl_vm_drop(vm);
|
||||
free(vm);
|
||||
}
|
||||
|
||||
int vm_load_from_buffer(void *p, const void *buf, unsigned long long len) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
spl_prog_t *prog;
|
||||
if (!vm || !buf)
|
||||
return -1;
|
||||
prog = (spl_prog_t *)malloc(sizeof(spl_prog_t));
|
||||
if (!prog)
|
||||
return -1;
|
||||
if (spl_prog_load_from_buffer((const char *)buf, (usize)len, prog) != 0) {
|
||||
free(prog);
|
||||
return -1;
|
||||
}
|
||||
spl_syscall_register(prog);
|
||||
if (spl_vm_load_prog(vm, prog) != 0) {
|
||||
spl_prog_drop(prog);
|
||||
free(prog);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vm_push(void *p, unsigned long long val) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
if (!vm)
|
||||
return;
|
||||
if (vm->sp >= vm->config.max_stack_depth)
|
||||
return;
|
||||
vm->stacks.data[vm->sp++] = (spl_vm_val_t)val;
|
||||
}
|
||||
|
||||
int vm_call(void *p, const char *name, unsigned long long nargs) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
spl_vm_func_t *func;
|
||||
if (!vm || !name)
|
||||
return -1;
|
||||
func = spl_prog_get_func(vm->prog, name);
|
||||
if (!func)
|
||||
return -1;
|
||||
if (vm->cp >= vm->config.max_call_depth)
|
||||
return -1;
|
||||
/* 入口调用:ip=-1 作 sentinel;实参由 vm_push 压栈。 */
|
||||
vm->ip = (usize)-1;
|
||||
vm->cp = 0;
|
||||
vm->fp = 0;
|
||||
vm->frames.data[0].saved_sp = vm->sp - (usize)nargs;
|
||||
vm->frames.data[0].saved_fp = vm->fp;
|
||||
vm->frames.data[0].saved_ip = vm->ip;
|
||||
vm->frames.data[0].nargs = (spl_vm_val_t)nargs;
|
||||
vm->cp = 1;
|
||||
vm->ip = (usize)func->address;
|
||||
vm->fp = vm->sp - (usize)nargs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vm_run(void *p) {
|
||||
spl_vm_t *vm = (spl_vm_t *)p;
|
||||
if (!vm)
|
||||
return -1;
|
||||
spl_vm_run_until(vm, 0);
|
||||
return (int)vm->exit_code;
|
||||
}
|
||||
#endif /* SPL_VM_EMBED_IMPLIMENTATION */
|
||||
|
||||
#endif /* __SPL_VM_EMBED_H__ */
|
||||
@@ -0,0 +1,49 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 时间/参考量接口(原生 vm_timestamp,case 探测风格,不假设一定可用)。
|
||||
// available() -> 1 = 时间可用;0 = 不可用(其余 time 相关返回值恒为 0)
|
||||
// sec() -> 当前秒
|
||||
// ns() -> 当前纳秒部分
|
||||
// steps() -> VM 已执行指令数(参考量)
|
||||
// now_ns() -> 完整纳秒时间戳(单次调用;纪元纳秒 < 2^64,无需 i128)
|
||||
|
||||
#extern(vm) fn vm_timestamp(kind: usize): usize;
|
||||
|
||||
fn available(): usize {
|
||||
ret vm_timestamp(0);
|
||||
}
|
||||
|
||||
fn sec(): usize {
|
||||
ret vm_timestamp(1);
|
||||
}
|
||||
|
||||
fn ns(): usize {
|
||||
ret vm_timestamp(2);
|
||||
}
|
||||
|
||||
fn steps(): usize {
|
||||
ret vm_timestamp(3);
|
||||
}
|
||||
|
||||
fn calls(): usize {
|
||||
ret vm_timestamp(5);
|
||||
}
|
||||
|
||||
fn now_ns(): usize {
|
||||
ret vm_timestamp(4);
|
||||
}
|
||||
|
||||
fn main(): void {
|
||||
var has = available();
|
||||
if has != 0 {
|
||||
var a = now_ns();
|
||||
var b = now_ns();
|
||||
@assert(b >= a, "now monotonic");
|
||||
}
|
||||
|
||||
var s0 = steps();
|
||||
var i: usize = 0;
|
||||
while i < 1000 { i = i + 1; }
|
||||
var s1 = steps();
|
||||
@assert(s1 >= s0, "steps monotonic");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
type spl_vm_val_t = u64; // usize
|
||||
|
||||
#extern(vm) fn vm_new(): *any;
|
||||
#extern(vm) fn vm_drop(vm: *any): void;
|
||||
|
||||
#extern(vm) fn vm_load(vm: *any, fpath: *u8): void;
|
||||
#extern(vm) fn vm_load_from_buffer(vm: *any, buf: *any, len: usize): i32;
|
||||
|
||||
#extern(vm) fn vm_push(vm: *any, val: spl_vm_val_t): void;
|
||||
// TODO
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,427 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 词法单元:用带载荷 ADT 表达 TokenKind;关键词/算符各自独立 enum。
|
||||
// 整型与字符在词法期解码并随载荷携带;文本一律借用源切片(不复制)。
|
||||
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
|
||||
type Keyword = enum {
|
||||
Any, As, Bool, Break, Catch, Comptime, Const, Continue, Defer, Else, Enum,
|
||||
Errdefer, False, Fn, For, If, Loop, Match, Null, Ret, Shape, Struct, True,
|
||||
Try, Type, Undefined, Union, Var, While, Underscore,
|
||||
}
|
||||
|
||||
type KeywordResult = enum {
|
||||
Yes: Keyword,
|
||||
No,
|
||||
}
|
||||
|
||||
type Punct = enum {
|
||||
Eq, Assign, AddAssign, Add, SubAssign, Sub, MulAssign, Mul, DivAssign, Div,
|
||||
ModAssign, Mod, RightArrow, LeftArrow, ArrowBoth, FatArrowBoth, RightFatArrow,
|
||||
AndAnd, AndAssign, And, OrOr, OrAssign, Or, XorAssign, Xor,
|
||||
LShAssign, LSh, Le, Lt, RShAssign, RSh, Ge, Gt, Not, Neq, BitNot,
|
||||
LBracket, RBracket, LParen, RParen, LBrace, RBrace,
|
||||
Semicolon, Comma, Colon, ColonAssign, Dot, Range, Ellipsis, Cond, Sharp, At, Dollar,
|
||||
}
|
||||
|
||||
type Radix = enum {
|
||||
Dec,
|
||||
Bin,
|
||||
Oct,
|
||||
Hex,
|
||||
}
|
||||
|
||||
type TokenKind = enum {
|
||||
Keyword: Keyword,
|
||||
Punct: Punct,
|
||||
Ident,
|
||||
Int: Radix, // 仅记进制;数值由 parser 按任意宽度(mp)解析
|
||||
Float,
|
||||
Char: u8,
|
||||
Str,
|
||||
LineComment,
|
||||
Endline,
|
||||
Eof,
|
||||
Unknown: u8,
|
||||
}
|
||||
|
||||
type Token = @self;
|
||||
|
||||
kind: TokenKind,
|
||||
text: ByteView,
|
||||
line: usize,
|
||||
col: usize,
|
||||
offset: usize, // 源文件字节偏移(供 AST Span 使用)
|
||||
|
||||
fn make(kind: TokenKind, text: ByteView, line: usize, col: usize, offset: usize): Token {
|
||||
ret Token { .kind = kind, .text = text, .line = line, .col = col, .offset = offset };
|
||||
}
|
||||
|
||||
fn is_trivia(kind: TokenKind): bool {
|
||||
match kind {
|
||||
.Endline => { ret true; },
|
||||
.LineComment => { ret true; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
}
|
||||
|
||||
// 先按长度与首字节分派,再做完整比较,避免对每个标识符做全表串比较。
|
||||
fn keyword_of(text: ByteView): KeywordResult {
|
||||
var n = text.len;
|
||||
if n == 0 { ret .No; }
|
||||
var c0 = text.ptr[0];
|
||||
if n == 2 {
|
||||
if c0 == 'a' && is(text, "as") { ret .Yes(.As); }
|
||||
if c0 == 'f' && is(text, "fn") { ret .Yes(.Fn); }
|
||||
if c0 == 'i' && is(text, "if") { ret .Yes(.If); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 3 {
|
||||
if c0 == 'a' && is(text, "any") { ret .Yes(.Any); }
|
||||
if c0 == 'f' && is(text, "for") { ret .Yes(.For); }
|
||||
if c0 == 'r' && is(text, "ret") { ret .Yes(.Ret); }
|
||||
if c0 == 't' && is(text, "try") { ret .Yes(.Try); }
|
||||
if c0 == 'v' && is(text, "var") { ret .Yes(.Var); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 4 {
|
||||
if c0 == 'b' && is(text, "bool") { ret .Yes(.Bool); }
|
||||
if c0 == 'e' && is(text, "else") { ret .Yes(.Else); }
|
||||
if c0 == 'e' && is(text, "enum") { ret .Yes(.Enum); }
|
||||
if c0 == 'l' && is(text, "loop") { ret .Yes(.Loop); }
|
||||
if c0 == 'n' && is(text, "null") { ret .Yes(.Null); }
|
||||
if c0 == 't' && is(text, "true") { ret .Yes(.True); }
|
||||
if c0 == 't' && is(text, "type") { ret .Yes(.Type); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 5 {
|
||||
if c0 == 'b' && is(text, "break") { ret .Yes(.Break); }
|
||||
if c0 == 'c' && is(text, "catch") { ret .Yes(.Catch); }
|
||||
if c0 == 'c' && is(text, "const") { ret .Yes(.Const); }
|
||||
if c0 == 'd' && is(text, "defer") { ret .Yes(.Defer); }
|
||||
if c0 == 'f' && is(text, "false") { ret .Yes(.False); }
|
||||
if c0 == 'm' && is(text, "match") { ret .Yes(.Match); }
|
||||
if c0 == 's' && is(text, "shape") { ret .Yes(.Shape); }
|
||||
if c0 == 'u' && is(text, "union") { ret .Yes(.Union); }
|
||||
if c0 == 'w' && is(text, "while") { ret .Yes(.While); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 6 {
|
||||
if c0 == 's' && is(text, "struct") { ret .Yes(.Struct); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 8 {
|
||||
if c0 == 'c' && is(text, "comptime") { ret .Yes(.Comptime); }
|
||||
if c0 == 'c' && is(text, "continue") { ret .Yes(.Continue); }
|
||||
if c0 == 'e' && is(text, "errdefer") { ret .Yes(.Errdefer); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 9 {
|
||||
if c0 == 'u' && is(text, "undefined") { ret .Yes(.Undefined); }
|
||||
ret .No;
|
||||
}
|
||||
if n == 1 {
|
||||
if c0 == '_' && is(text, "_") { ret .Yes(.Underscore); }
|
||||
ret .No;
|
||||
}
|
||||
ret .No;
|
||||
}
|
||||
|
||||
// ---- token 类型名(与 stage1 spl_tok_type_name 逐字一致,供 dump/diff 使用)----
|
||||
|
||||
fn keyword_name(kw: Keyword): []u8 {
|
||||
match kw {
|
||||
.Any => { ret "KW_ANY"; },
|
||||
.As => { ret "KW_AS"; },
|
||||
.Bool => { ret "KW_BOOL"; },
|
||||
.Break => { ret "KW_BREAK"; },
|
||||
.Catch => { ret "KW_CATCH"; },
|
||||
.Comptime => { ret "KW_COMPTIME"; },
|
||||
.Const => { ret "KW_CONST"; },
|
||||
.Continue => { ret "KW_CONTINUE"; },
|
||||
.Defer => { ret "KW_DEFER"; },
|
||||
.Else => { ret "KW_ELSE"; },
|
||||
.Enum => { ret "KW_ENUM"; },
|
||||
.Errdefer => { ret "KW_ERRDEFER"; },
|
||||
.False => { ret "KW_FALSE"; },
|
||||
.Fn => { ret "KW_FN"; },
|
||||
.For => { ret "KW_FOR"; },
|
||||
.If => { ret "KW_IF"; },
|
||||
.Loop => { ret "KW_LOOP"; },
|
||||
.Match => { ret "KW_MATCH"; },
|
||||
.Null => { ret "KW_NULL"; },
|
||||
.Ret => { ret "KW_RET"; },
|
||||
.Shape => { ret "KW_SHAPE"; },
|
||||
.Struct => { ret "KW_STRUCT"; },
|
||||
.True => { ret "KW_TRUE"; },
|
||||
.Try => { ret "KW_TRY"; },
|
||||
.Type => { ret "KW_TYPE"; },
|
||||
.Undefined => { ret "KW_UNDEFINED"; },
|
||||
.Union => { ret "KW_UNION"; },
|
||||
.Var => { ret "KW_VAR"; },
|
||||
.While => { ret "KW_WHILE"; },
|
||||
.Underscore => { ret "KW_UNDERSCORE"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn punct_name(punct: Punct): []u8 {
|
||||
match punct {
|
||||
.Eq => { ret "TOK_EQ"; },
|
||||
.Assign => { ret "TOK_ASSIGN"; },
|
||||
.AddAssign => { ret "TOK_ASSIGN_ADD"; },
|
||||
.Add => { ret "TOK_ADD"; },
|
||||
.SubAssign => { ret "TOK_ASSIGN_SUB"; },
|
||||
.Sub => { ret "TOK_SUB"; },
|
||||
.MulAssign => { ret "TOK_ASSIGN_MUL"; },
|
||||
.Mul => { ret "TOK_MUL"; },
|
||||
.DivAssign => { ret "TOK_ASSIGN_DIV"; },
|
||||
.Div => { ret "TOK_DIV"; },
|
||||
.ModAssign => { ret "TOK_ASSIGN_MOD"; },
|
||||
.Mod => { ret "TOK_MOD"; },
|
||||
.RightArrow => { ret "TOK_RIGHT_ARROW"; },
|
||||
.LeftArrow => { ret "TOK_LEFT_ARROW"; },
|
||||
.ArrowBoth => { ret "TOK_ARROW_BOTH"; },
|
||||
.FatArrowBoth => { ret "TOK_FAT_ARROW_BOTH"; },
|
||||
.RightFatArrow => { ret "TOK_RIGHT_FAT_ARROW"; },
|
||||
.AndAnd => { ret "TOK_AND_AND"; },
|
||||
.AndAssign => { ret "TOK_ASSIGN_AND"; },
|
||||
.And => { ret "TOK_AND"; },
|
||||
.OrOr => { ret "TOK_OR_OR"; },
|
||||
.OrAssign => { ret "TOK_ASSIGN_OR"; },
|
||||
.Or => { ret "TOK_OR"; },
|
||||
.XorAssign => { ret "TOK_ASSIGN_XOR"; },
|
||||
.Xor => { ret "TOK_XOR"; },
|
||||
.LShAssign => { ret "TOK_ASSIGN_L_SH"; },
|
||||
.LSh => { ret "TOK_L_SH"; },
|
||||
.Le => { ret "TOK_LE"; },
|
||||
.Lt => { ret "TOK_LT"; },
|
||||
.RShAssign => { ret "TOK_ASSIGN_R_SH"; },
|
||||
.RSh => { ret "TOK_R_SH"; },
|
||||
.Ge => { ret "TOK_GE"; },
|
||||
.Gt => { ret "TOK_GT"; },
|
||||
.Not => { ret "TOK_NOT"; },
|
||||
.Neq => { ret "TOK_NEQ"; },
|
||||
.BitNot => { ret "TOK_BIT_NOT"; },
|
||||
.LBracket => { ret "TOK_L_BRACKET"; },
|
||||
.RBracket => { ret "TOK_R_BRACKET"; },
|
||||
.LParen => { ret "TOK_L_PAREN"; },
|
||||
.RParen => { ret "TOK_R_PAREN"; },
|
||||
.LBrace => { ret "TOK_L_BRACE"; },
|
||||
.RBrace => { ret "TOK_R_BRACE"; },
|
||||
.Semicolon => { ret "TOK_SEMICOLON"; },
|
||||
.Comma => { ret "TOK_COMMA"; },
|
||||
.Colon => { ret "TOK_COLON"; },
|
||||
.ColonAssign => { ret "TOK_COLON_ASSIGN"; },
|
||||
.Dot => { ret "TOK_DOT"; },
|
||||
.Range => { ret "TOK_RANGE"; },
|
||||
.Ellipsis => { ret "TOK_ELLIPSIS"; },
|
||||
.Cond => { ret "TOK_COND"; },
|
||||
.Sharp => { ret "TOK_SHARP"; },
|
||||
.At => { ret "TOK_AT"; },
|
||||
.Dollar => { ret "TOK_DOLLAR"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn kind_name(kind: TokenKind): []u8 {
|
||||
match kind {
|
||||
.Keyword(kw) => { ret keyword_name(kw); },
|
||||
.Punct(punct) => { ret punct_name(punct); },
|
||||
.Ident => { ret "TOK_IDENT"; },
|
||||
.Int(value) => { ret "TOK_INT_LITERAL"; },
|
||||
.Float => { ret "TOK_FLOAT_LITERAL"; },
|
||||
.Char(value) => { ret "TOK_CHAR_LITERAL"; },
|
||||
.Str => { ret "TOK_STRING_LITERAL"; },
|
||||
.LineComment => { ret "TOK_LINE_COMMENT"; },
|
||||
.Endline => { ret "TOK_ENDLINE"; },
|
||||
.Eof => { ret "TOK_EOF"; },
|
||||
.Unknown(value) => { ret "TOK_UNKNOWN"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn view(text: []u8): ByteView {
|
||||
ret ByteView.from_slice(text);
|
||||
}
|
||||
|
||||
fn is(text: ByteView, literal: []u8): bool {
|
||||
ret ByteView.eq(text, view(literal));
|
||||
}
|
||||
|
||||
fn expect_keyword(result: KeywordResult, expected: Keyword) {
|
||||
match result {
|
||||
.Yes(kw) => { @assert(@enum2int(kw) == @enum2int(expected), "keyword"); },
|
||||
.No => { @assert(false, "expected keyword"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_keywords() {
|
||||
expect_keyword(keyword_of(view("fn")), .Fn);
|
||||
expect_keyword(keyword_of(view("while")), .While);
|
||||
expect_keyword(keyword_of(view("_")), .Underscore);
|
||||
match keyword_of(view("foo")) {
|
||||
.Yes(kw) => { @assert(false, "foo is not a keyword"); },
|
||||
.No => {},
|
||||
}
|
||||
match keyword_of(view("")) {
|
||||
.Yes(kw) => { @assert(false, "empty is not a keyword"); },
|
||||
.No => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_adt_payloads() {
|
||||
var t = make(.Int(.Hex), view("0xFF"), 3, 5, 0);
|
||||
@assert(t.line == 3 && t.col == 5, "position");
|
||||
match t.kind {
|
||||
.Int(radix) => {
|
||||
var hex: Radix = .Hex;
|
||||
@assert(@enum2int(radix) == @enum2int(hex), "int radix");
|
||||
},
|
||||
_ => { @assert(false, "expected int"); },
|
||||
}
|
||||
|
||||
var c = make(.Char('a'), view("'a'"), 1, 1, 0);
|
||||
match c.kind {
|
||||
.Char(value) => { @assert(value == 'a', "char value"); },
|
||||
_ => { @assert(false, "expected char"); },
|
||||
}
|
||||
|
||||
var p = make(.Punct(.ArrowBoth), view("<->"), 1, 1, 0);
|
||||
match p.kind {
|
||||
.Punct(punct) => {
|
||||
var both: Punct = .ArrowBoth;
|
||||
@assert(@enum2int(punct) == @enum2int(both), "punct");
|
||||
},
|
||||
_ => { @assert(false, "expected punct"); },
|
||||
}
|
||||
|
||||
var k = make(.Keyword(.Ret), view("ret"), 1, 1, 0);
|
||||
match k.kind {
|
||||
.Keyword(kw) => {
|
||||
var ret_kw: Keyword = .Ret;
|
||||
@assert(@enum2int(kw) == @enum2int(ret_kw), "keyword kind");
|
||||
},
|
||||
_ => { @assert(false, "expected keyword"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn test_trivia() {
|
||||
@assert(is_trivia(.Endline), "endline trivia");
|
||||
@assert(is_trivia(.LineComment), "comment trivia");
|
||||
@assert(!is_trivia(.Ident), "ident not trivia");
|
||||
@assert(!is_trivia(.Eof), "eof not trivia");
|
||||
}
|
||||
|
||||
fn expect_kind_name(kind: TokenKind, expected: []u8) {
|
||||
@assert(ByteView.eq(ByteView.from_slice(kind_name(kind)),
|
||||
ByteView.from_slice(expected)),
|
||||
"token kind name");
|
||||
}
|
||||
|
||||
fn test_kind_names() {
|
||||
expect_kind_name(.Keyword(.Any), "KW_ANY");
|
||||
expect_kind_name(.Keyword(.As), "KW_AS");
|
||||
expect_kind_name(.Keyword(.Bool), "KW_BOOL");
|
||||
expect_kind_name(.Keyword(.Break), "KW_BREAK");
|
||||
expect_kind_name(.Keyword(.Catch), "KW_CATCH");
|
||||
expect_kind_name(.Keyword(.Comptime), "KW_COMPTIME");
|
||||
expect_kind_name(.Keyword(.Const), "KW_CONST");
|
||||
expect_kind_name(.Keyword(.Continue), "KW_CONTINUE");
|
||||
expect_kind_name(.Keyword(.Defer), "KW_DEFER");
|
||||
expect_kind_name(.Keyword(.Else), "KW_ELSE");
|
||||
expect_kind_name(.Keyword(.Enum), "KW_ENUM");
|
||||
expect_kind_name(.Keyword(.Errdefer), "KW_ERRDEFER");
|
||||
expect_kind_name(.Keyword(.False), "KW_FALSE");
|
||||
expect_kind_name(.Keyword(.Fn), "KW_FN");
|
||||
expect_kind_name(.Keyword(.For), "KW_FOR");
|
||||
expect_kind_name(.Keyword(.If), "KW_IF");
|
||||
expect_kind_name(.Keyword(.Loop), "KW_LOOP");
|
||||
expect_kind_name(.Keyword(.Match), "KW_MATCH");
|
||||
expect_kind_name(.Keyword(.Null), "KW_NULL");
|
||||
expect_kind_name(.Keyword(.Ret), "KW_RET");
|
||||
expect_kind_name(.Keyword(.Shape), "KW_SHAPE");
|
||||
expect_kind_name(.Keyword(.Struct), "KW_STRUCT");
|
||||
expect_kind_name(.Keyword(.True), "KW_TRUE");
|
||||
expect_kind_name(.Keyword(.Try), "KW_TRY");
|
||||
expect_kind_name(.Keyword(.Type), "KW_TYPE");
|
||||
expect_kind_name(.Keyword(.Undefined), "KW_UNDEFINED");
|
||||
expect_kind_name(.Keyword(.Union), "KW_UNION");
|
||||
expect_kind_name(.Keyword(.Var), "KW_VAR");
|
||||
expect_kind_name(.Keyword(.While), "KW_WHILE");
|
||||
expect_kind_name(.Keyword(.Underscore), "KW_UNDERSCORE");
|
||||
|
||||
expect_kind_name(.Punct(.Eq), "TOK_EQ");
|
||||
expect_kind_name(.Punct(.Assign), "TOK_ASSIGN");
|
||||
expect_kind_name(.Punct(.AddAssign), "TOK_ASSIGN_ADD");
|
||||
expect_kind_name(.Punct(.Add), "TOK_ADD");
|
||||
expect_kind_name(.Punct(.SubAssign), "TOK_ASSIGN_SUB");
|
||||
expect_kind_name(.Punct(.Sub), "TOK_SUB");
|
||||
expect_kind_name(.Punct(.MulAssign), "TOK_ASSIGN_MUL");
|
||||
expect_kind_name(.Punct(.Mul), "TOK_MUL");
|
||||
expect_kind_name(.Punct(.DivAssign), "TOK_ASSIGN_DIV");
|
||||
expect_kind_name(.Punct(.Div), "TOK_DIV");
|
||||
expect_kind_name(.Punct(.ModAssign), "TOK_ASSIGN_MOD");
|
||||
expect_kind_name(.Punct(.Mod), "TOK_MOD");
|
||||
expect_kind_name(.Punct(.RightArrow), "TOK_RIGHT_ARROW");
|
||||
expect_kind_name(.Punct(.LeftArrow), "TOK_LEFT_ARROW");
|
||||
expect_kind_name(.Punct(.ArrowBoth), "TOK_ARROW_BOTH");
|
||||
expect_kind_name(.Punct(.FatArrowBoth), "TOK_FAT_ARROW_BOTH");
|
||||
expect_kind_name(.Punct(.RightFatArrow), "TOK_RIGHT_FAT_ARROW");
|
||||
expect_kind_name(.Punct(.AndAnd), "TOK_AND_AND");
|
||||
expect_kind_name(.Punct(.AndAssign), "TOK_ASSIGN_AND");
|
||||
expect_kind_name(.Punct(.And), "TOK_AND");
|
||||
expect_kind_name(.Punct(.OrOr), "TOK_OR_OR");
|
||||
expect_kind_name(.Punct(.OrAssign), "TOK_ASSIGN_OR");
|
||||
expect_kind_name(.Punct(.Or), "TOK_OR");
|
||||
expect_kind_name(.Punct(.XorAssign), "TOK_ASSIGN_XOR");
|
||||
expect_kind_name(.Punct(.Xor), "TOK_XOR");
|
||||
expect_kind_name(.Punct(.LShAssign), "TOK_ASSIGN_L_SH");
|
||||
expect_kind_name(.Punct(.LSh), "TOK_L_SH");
|
||||
expect_kind_name(.Punct(.Le), "TOK_LE");
|
||||
expect_kind_name(.Punct(.Lt), "TOK_LT");
|
||||
expect_kind_name(.Punct(.RShAssign), "TOK_ASSIGN_R_SH");
|
||||
expect_kind_name(.Punct(.RSh), "TOK_R_SH");
|
||||
expect_kind_name(.Punct(.Ge), "TOK_GE");
|
||||
expect_kind_name(.Punct(.Gt), "TOK_GT");
|
||||
expect_kind_name(.Punct(.Not), "TOK_NOT");
|
||||
expect_kind_name(.Punct(.Neq), "TOK_NEQ");
|
||||
expect_kind_name(.Punct(.BitNot), "TOK_BIT_NOT");
|
||||
expect_kind_name(.Punct(.LBracket), "TOK_L_BRACKET");
|
||||
expect_kind_name(.Punct(.RBracket), "TOK_R_BRACKET");
|
||||
expect_kind_name(.Punct(.LParen), "TOK_L_PAREN");
|
||||
expect_kind_name(.Punct(.RParen), "TOK_R_PAREN");
|
||||
expect_kind_name(.Punct(.LBrace), "TOK_L_BRACE");
|
||||
expect_kind_name(.Punct(.RBrace), "TOK_R_BRACE");
|
||||
expect_kind_name(.Punct(.Semicolon), "TOK_SEMICOLON");
|
||||
expect_kind_name(.Punct(.Comma), "TOK_COMMA");
|
||||
expect_kind_name(.Punct(.Colon), "TOK_COLON");
|
||||
expect_kind_name(.Punct(.ColonAssign), "TOK_COLON_ASSIGN");
|
||||
expect_kind_name(.Punct(.Dot), "TOK_DOT");
|
||||
expect_kind_name(.Punct(.Range), "TOK_RANGE");
|
||||
expect_kind_name(.Punct(.Ellipsis), "TOK_ELLIPSIS");
|
||||
expect_kind_name(.Punct(.Cond), "TOK_COND");
|
||||
expect_kind_name(.Punct(.Sharp), "TOK_SHARP");
|
||||
expect_kind_name(.Punct(.At), "TOK_AT");
|
||||
expect_kind_name(.Punct(.Dollar), "TOK_DOLLAR");
|
||||
|
||||
expect_kind_name(.Ident, "TOK_IDENT");
|
||||
expect_kind_name(.Int(.Dec), "TOK_INT_LITERAL");
|
||||
expect_kind_name(.Float, "TOK_FLOAT_LITERAL");
|
||||
expect_kind_name(.Char('a'), "TOK_CHAR_LITERAL");
|
||||
expect_kind_name(.Str, "TOK_STRING_LITERAL");
|
||||
expect_kind_name(.LineComment, "TOK_LINE_COMMENT");
|
||||
expect_kind_name(.Endline, "TOK_ENDLINE");
|
||||
expect_kind_name(.Eof, "TOK_EOF");
|
||||
expect_kind_name(.Unknown(0), "TOK_UNKNOWN");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_keywords();
|
||||
test_adt_payloads();
|
||||
test_trivia();
|
||||
test_kind_names();
|
||||
}
|
||||
@@ -0,0 +1,910 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// IR 类型表(移植自 stage1/spl_type.c)。类型按结构 hash-consing(intern),
|
||||
// id 0 为保留哨兵(Error)。节点借用式持有:名称是 ByteView(指向源/AST),
|
||||
// 不复制字符串;聚合成员与函数参数的 Vec 由本表拥有,drop 时统一释放。
|
||||
//
|
||||
// hash 只用于选桶;相等永远由结构比较决定。桶以「完整 hash -> 链头 id」的
|
||||
// Map 存储,节点自带 hash_next 串链(对应 stage1 的 spl_fold)。
|
||||
|
||||
type .{ .Error } = @import("utils/error.spl");
|
||||
type .{ .Vec } = @import("utils/vec.spl");
|
||||
type .{ .Map, .hash_kind_usize } = @import("utils/map.spl");
|
||||
type .{ .ByteView } = @import("utils/byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("utils/fmt.spl");
|
||||
type .{ .hash_usize, .eq_usize } = @import("utils/hash.spl");
|
||||
|
||||
type LayoutMode = enum {
|
||||
Auto,
|
||||
ExternC,
|
||||
Packed,
|
||||
}
|
||||
|
||||
type Member = struct {
|
||||
name: ByteView,
|
||||
type_id: usize,
|
||||
ast_node_ref: usize,
|
||||
}
|
||||
|
||||
type Agg = struct {
|
||||
mode: LayoutMode,
|
||||
fixed_align_bits: usize,
|
||||
tag_type: usize,
|
||||
variants: Vec, // Member
|
||||
}
|
||||
|
||||
type AggKind = enum {
|
||||
Struct,
|
||||
Union,
|
||||
Enum,
|
||||
Shape,
|
||||
}
|
||||
|
||||
// 与 stage1 spl_type_node_kind_t 一一对应;Forall/Exists 保留(暂打印 quant)。
|
||||
type TypeNode = enum {
|
||||
Error,
|
||||
Never,
|
||||
Any,
|
||||
Unit,
|
||||
Infer,
|
||||
Bool,
|
||||
Int: struct { bits: usize, is_signed: bool },
|
||||
Float: struct { bits: usize },
|
||||
Ptr: struct { pointee: usize },
|
||||
Array: struct { element: usize, len: usize },
|
||||
Struct: Agg,
|
||||
Union: Agg,
|
||||
Enum: Agg,
|
||||
Fn: struct { params: Vec, ret_type: usize }, // params: usize
|
||||
Forall,
|
||||
Exists,
|
||||
Shape: Agg,
|
||||
}
|
||||
|
||||
// 表项 = 节点 + intern 桶链指针(hash_next 不参与结构相等)。
|
||||
type TypeEntry = struct {
|
||||
hash_next: usize,
|
||||
node: TypeNode,
|
||||
}
|
||||
|
||||
type TypeTable = @self;
|
||||
|
||||
table: Vec, // TypeEntry
|
||||
fold: Map, // usize hash -> usize chain head
|
||||
|
||||
type TypeTableResult = enum {
|
||||
Ok: TypeTable,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
// ---- hash(FNV 风格;值不必与 stage1 相同,只要结构一致即可)----
|
||||
|
||||
fn seed(): usize {
|
||||
ret 14695981039346656037;
|
||||
}
|
||||
|
||||
fn mix(h: usize, x: usize): usize {
|
||||
ret (h ^ x) * 1099511628211;
|
||||
}
|
||||
|
||||
fn mode_code(mode: LayoutMode): usize {
|
||||
var m: LayoutMode = mode;
|
||||
ret @enum2int(m) as usize;
|
||||
}
|
||||
|
||||
fn hash_agg(h0: usize, agg: Agg): usize {
|
||||
var h = mix(h0, mode_code(agg.mode));
|
||||
h = mix(h, agg.fixed_align_bits);
|
||||
h = mix(h, agg.tag_type);
|
||||
h = mix(h, agg.variants.len);
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null {
|
||||
h = mix(h, m.type_id);
|
||||
h = mix_bytes(h, m.name);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn mix_bytes(h0: usize, view: ByteView): usize {
|
||||
var h = h0;
|
||||
var i: usize = 0;
|
||||
while i < view.len {
|
||||
h = mix(h, view.ptr[i] as usize);
|
||||
i = i + 1;
|
||||
}
|
||||
ret mix(h, 0); // 终止符参与混合,避免名称拼接歧义
|
||||
}
|
||||
|
||||
fn hash_node(n: *TypeNode): usize {
|
||||
var h = seed();
|
||||
match n.* {
|
||||
.Error => { h = mix(h, 0); },
|
||||
.Never => { h = mix(h, 1); },
|
||||
.Any => { h = mix(h, 2); },
|
||||
.Unit => { h = mix(h, 3); },
|
||||
.Infer => { h = mix(h, 4); },
|
||||
.Bool => { h = mix(h, 5); },
|
||||
.Int(x) => {
|
||||
h = mix(h, 6);
|
||||
h = mix(h, x.bits);
|
||||
h = mix(h, if x.is_signed { 1 } else { 0 });
|
||||
},
|
||||
.Float(x) => {
|
||||
h = mix(h, 7);
|
||||
h = mix(h, x.bits);
|
||||
},
|
||||
.Ptr(x) => {
|
||||
h = mix(h, 8);
|
||||
h = mix(h, x.pointee);
|
||||
},
|
||||
.Array(x) => {
|
||||
h = mix(h, 9);
|
||||
h = mix(h, x.element);
|
||||
h = mix(h, x.len);
|
||||
},
|
||||
.Struct(x) => { h = hash_agg(mix(h, 10), x); },
|
||||
.Union(x) => { h = hash_agg(mix(h, 11), x); },
|
||||
.Enum(x) => { h = hash_agg(mix(h, 12), x); },
|
||||
.Fn(x) => {
|
||||
h = mix(h, 13);
|
||||
h = mix(h, x.ret_type);
|
||||
h = mix(h, x.params.len);
|
||||
var i: usize = 0;
|
||||
while i < x.params.len {
|
||||
h = mix(h, usize_at(x.params, i));
|
||||
i = i + 1;
|
||||
}
|
||||
},
|
||||
.Forall => { h = mix(h, 14); },
|
||||
.Exists => { h = mix(h, 15); },
|
||||
.Shape(x) => { h = hash_agg(mix(h, 16), x); },
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn tag(n: *TypeNode): usize {
|
||||
match n.* {
|
||||
.Error => { ret 0; },
|
||||
.Never => { ret 1; },
|
||||
.Any => { ret 2; },
|
||||
.Unit => { ret 3; },
|
||||
.Infer => { ret 4; },
|
||||
.Bool => { ret 5; },
|
||||
.Int(x) => { ret 6; },
|
||||
.Float(x) => { ret 7; },
|
||||
.Ptr(x) => { ret 8; },
|
||||
.Array(x) => { ret 9; },
|
||||
.Struct(x) => { ret 10; },
|
||||
.Union(x) => { ret 11; },
|
||||
.Enum(x) => { ret 12; },
|
||||
.Fn(x) => { ret 13; },
|
||||
.Forall => { ret 14; },
|
||||
.Exists => { ret 15; },
|
||||
.Shape(x) => { ret 16; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn agg_eq(x: Agg, y: Agg): bool {
|
||||
if mode_code(x.mode) != mode_code(y.mode) { ret false; }
|
||||
if x.fixed_align_bits != y.fixed_align_bits { ret false; }
|
||||
if x.tag_type != y.tag_type { ret false; }
|
||||
if x.variants.len != y.variants.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < x.variants.len {
|
||||
var ma = member_at(&x.variants, i);
|
||||
var mb = member_at(&y.variants, i);
|
||||
if ma == null || mb == null { ret false; }
|
||||
if ma.type_id != mb.type_id { ret false; }
|
||||
if !ByteView.eq(ma.name, mb.name) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn node_eq(a: *TypeNode, b: *TypeNode): bool {
|
||||
if tag(a) != tag(b) { ret false; }
|
||||
match a.* {
|
||||
.Int(x) => {
|
||||
match b.* {
|
||||
.Int(y) => { ret x.bits == y.bits && x.is_signed == y.is_signed; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Float(x) => {
|
||||
match b.* {
|
||||
.Float(y) => { ret x.bits == y.bits; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Ptr(x) => {
|
||||
match b.* {
|
||||
.Ptr(y) => { ret x.pointee == y.pointee; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Array(x) => {
|
||||
match b.* {
|
||||
.Array(y) => { ret x.element == y.element && x.len == y.len; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Struct(x) => {
|
||||
match b.* {
|
||||
.Struct(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Union(x) => {
|
||||
match b.* {
|
||||
.Union(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Enum(x) => {
|
||||
match b.* {
|
||||
.Enum(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Fn(x) => {
|
||||
match b.* {
|
||||
.Fn(y) => {
|
||||
if x.ret_type != y.ret_type { ret false; }
|
||||
if x.params.len != y.params.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < x.params.len {
|
||||
if usize_at(x.params, i) != usize_at(y.params, i) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Shape(x) => {
|
||||
match b.* {
|
||||
.Shape(y) => { ret agg_eq(x, y); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
_ => { ret true; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 释放节点内部拥有的 Vec(仅聚合变体与函数参数)。
|
||||
fn free_payload(n: *TypeNode) {
|
||||
match n.* {
|
||||
.Struct(x) => { Vec.drop(&x.variants); },
|
||||
.Union(x) => { Vec.drop(&x.variants); },
|
||||
.Enum(x) => { Vec.drop(&x.variants); },
|
||||
.Shape(x) => { Vec.drop(&x.variants); },
|
||||
.Fn(x) => { Vec.drop(&x.params); },
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 底层访问 ----
|
||||
|
||||
fn usize_at(v: Vec, i: usize): usize {
|
||||
match Vec.get(&v, i) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn member_at(v: *Vec, i: usize): *Member {
|
||||
if i >= v.len { ret null; }
|
||||
ret (v.data as *Member) + i;
|
||||
}
|
||||
|
||||
fn push_member(v: *Vec, name: []u8, type_id: usize) {
|
||||
var m = Member {
|
||||
.name = ByteView.from_slice(name),
|
||||
.type_id = type_id,
|
||||
.ast_node_ref = 0,
|
||||
};
|
||||
match Vec.push(v, &m as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push member"); },
|
||||
}
|
||||
}
|
||||
|
||||
// id 0 与越界均返回 null(对应 stage1 spl_type_node)。
|
||||
fn node(self: *TypeTable, id: usize): *TypeEntry {
|
||||
if id == 0 || id >= self.table.len { ret null; }
|
||||
ret (self.table.data as *TypeEntry) + id;
|
||||
}
|
||||
|
||||
// ---- 生命周期 ----
|
||||
|
||||
fn new(): TypeTableResult {
|
||||
var tt = TypeTable {
|
||||
.table = Vec.blank(@sizeof(TypeEntry), @alignof(TypeEntry)),
|
||||
.fold = Map.new(@sizeof(usize), @alignof(usize), @sizeof(usize), @alignof(usize),
|
||||
hash_kind_usize()),
|
||||
};
|
||||
var sentinel = TypeEntry { .hash_next = 0, .node = .Error };
|
||||
match Vec.push(&tt.table, &sentinel as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Vec.drop(&tt.table);
|
||||
Map.drop(&tt.fold);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
ret .Ok(tt);
|
||||
}
|
||||
|
||||
fn drop(self: *TypeTable) {
|
||||
var i: usize = 0;
|
||||
while i < self.table.len {
|
||||
var n = node(self, i);
|
||||
if n != null {
|
||||
free_payload(&n.node);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.table);
|
||||
Map.drop(&self.fold);
|
||||
}
|
||||
|
||||
// 预留一个空 ERROR 槽位作为身份锚点(不写 fold、不参与 hash-consing)。
|
||||
// 用于自引用聚合:先拿到稳定 id,稍后用 define 填内容。
|
||||
fn reserve(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Error;
|
||||
match Vec.push(&self.table, &n as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.table.len - 1;
|
||||
}
|
||||
|
||||
// 把 src 的内容填进已 reserve 的槽位;src 载荷所有权转移,之后 src 失效。
|
||||
fn define(self: *TypeTable, id: usize, src: *TypeNode) {
|
||||
if id == 0 { ret; }
|
||||
var slot = node(self, id);
|
||||
if slot == null { ret; }
|
||||
slot.node = src.*;
|
||||
src.* = .Error;
|
||||
}
|
||||
|
||||
// ---- intern ----
|
||||
|
||||
fn fold_find(self: *TypeTable, hash: usize, probe: *TypeNode): usize {
|
||||
var head: usize = 0;
|
||||
match Map.get(&self.fold, &hash as *u8, &head as *u8) {
|
||||
.Found => {},
|
||||
.NotFound => { ret 0; },
|
||||
}
|
||||
var cur = head;
|
||||
while cur != 0 {
|
||||
var n = node(self, cur);
|
||||
if n == null { ret 0; }
|
||||
if node_eq(&n.node, probe) { ret cur; }
|
||||
cur = n.hash_next;
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn fold_insert(self: *TypeTable, hash: usize, id: usize) {
|
||||
var old: usize = 0;
|
||||
match Map.get(&self.fold, &hash as *u8, &old as *u8) {
|
||||
.Found => {},
|
||||
.NotFound => { old = 0; },
|
||||
}
|
||||
var n = node(self, id);
|
||||
if n == null { ret; }
|
||||
n.hash_next = old;
|
||||
match Map.put(&self.fold, &hash as *u8, &id as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
}
|
||||
|
||||
// intern 一个 probe:命中则释放 probe 载荷并返回已有 id;否则复制入表。
|
||||
// probe 在返回后失效(载荷所有权转移,probe 被置为 Error)。失败返回 0。
|
||||
fn push(self: *TypeTable, probe: *TypeNode): usize {
|
||||
var hash = hash_node(probe);
|
||||
var existing = fold_find(self, hash, probe);
|
||||
if existing != 0 {
|
||||
free_payload(probe);
|
||||
probe.* = .Error;
|
||||
ret existing;
|
||||
}
|
||||
var entry = TypeEntry { .hash_next = 0, .node = probe.* };
|
||||
probe.* = .Error; // 载荷所有权已转移给 entry
|
||||
var id = self.table.len;
|
||||
match Vec.push(&self.table, &entry as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
free_payload(&entry.node);
|
||||
ret 0;
|
||||
},
|
||||
}
|
||||
fold_insert(self, hash, id);
|
||||
ret id;
|
||||
}
|
||||
|
||||
// ---- builders ----
|
||||
|
||||
fn make_error(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Error;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_never(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Never;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_any(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Any;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_unit(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Unit;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_infer(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Infer;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_bool(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Bool;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_forall(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Forall;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_exists(self: *TypeTable): usize {
|
||||
var n: TypeNode = .Exists;
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_int(self: *TypeTable, bits: usize, is_signed: bool): usize {
|
||||
var n: TypeNode = .Int(.{ .bits = bits, .is_signed = is_signed });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_float(self: *TypeTable, bits: usize): usize {
|
||||
var n: TypeNode = .Float(.{ .bits = bits });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_ptr(self: *TypeTable, pointee: usize): usize {
|
||||
var n: TypeNode = .Ptr(.{ .pointee = pointee });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
fn make_array(self: *TypeTable, element: usize, len: usize): usize {
|
||||
var n: TypeNode = .Array(.{ .element = element, .len = len });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
// params 所有权转移进表;返回后调用方不得再 drop。
|
||||
fn make_fn(self: *TypeTable, ret_tid: usize, params: Vec): usize {
|
||||
var n: TypeNode = .Fn(.{ .params = params, .ret_type = ret_tid });
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
// agg 所有权转移进表;返回后调用方不得再 drop。
|
||||
fn make_agg(self: *TypeTable, kind: AggKind, agg: Agg): usize {
|
||||
var n: TypeNode = .Error;
|
||||
match kind {
|
||||
.Struct => { n = .Struct(agg); },
|
||||
.Union => { n = .Union(agg); },
|
||||
.Enum => { n = .Enum(agg); },
|
||||
.Shape => { n = .Shape(agg); },
|
||||
}
|
||||
ret push(self, &n);
|
||||
}
|
||||
|
||||
// ---- 查询 ----
|
||||
|
||||
fn is_subtype(self: *TypeTable, sub: usize, super: usize): bool {
|
||||
if sub == 0 || super == 0 { ret false; }
|
||||
if sub == super { ret true; }
|
||||
|
||||
var ns = node(self, sub);
|
||||
var np = node(self, super);
|
||||
if ns == null || np == null { ret false; }
|
||||
|
||||
match ns.node {
|
||||
.Never => { ret true; },
|
||||
_ => {},
|
||||
}
|
||||
match np.node {
|
||||
.Any => { ret true; },
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match ns.node {
|
||||
.Int(x) => {
|
||||
match np.node {
|
||||
.Int(y) => {
|
||||
if x.bits == 0 { ret true; }
|
||||
if x.is_signed == y.is_signed { ret x.bits <= y.bits; }
|
||||
if !x.is_signed && y.is_signed { ret y.bits > x.bits; }
|
||||
ret false;
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
.Float(x) => {
|
||||
match np.node {
|
||||
.Float(y) => {
|
||||
if x.bits == 0 { ret true; }
|
||||
ret x.bits <= y.bits;
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
},
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn fn_param_tid(self: *TypeTable, fn_tid: usize, idx: usize): usize {
|
||||
var n = node(self, fn_tid);
|
||||
if n == null { ret 0; }
|
||||
match n.node {
|
||||
.Fn(x) => {
|
||||
if idx >= x.params.len { ret 0; }
|
||||
ret usize_at(x.params, idx);
|
||||
},
|
||||
_ => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn agg_find(agg: Agg, name: ByteView, out_idx: *usize): bool {
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m != null && ByteView.eq(m.name, name) {
|
||||
if out_idx != null { out_idx.* = i; }
|
||||
ret true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn agg_member_idx(self: *TypeTable, tid: usize, name: ByteView, out_idx: *usize): bool {
|
||||
var n = node(self, tid);
|
||||
if n == null { ret false; }
|
||||
match n.node {
|
||||
.Struct(x) => { ret agg_find(x, name, out_idx); },
|
||||
.Union(x) => { ret agg_find(x, name, out_idx); },
|
||||
.Enum(x) => { ret agg_find(x, name, out_idx); },
|
||||
.Shape(x) => { ret agg_find(x, name, out_idx); },
|
||||
_ => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// ---- dump(镜像 stage1 spl_type_pure_dump)----
|
||||
|
||||
fn push_out(buf: *ByteBuffer, bytes: []u8): bool {
|
||||
match ByteBuffer.append(buf, bytes) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn push_usize(buf: *ByteBuffer, value: usize): bool {
|
||||
match append_usize(buf, value, 10, false) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn dump_rec(self: *TypeTable, id: usize, out: *ByteBuffer, stack: *Vec): bool {
|
||||
var i: usize = 0;
|
||||
while i < stack.len {
|
||||
if usize_at(stack.*, i) == id {
|
||||
if !push_out(out, "<rec#") { ret false; }
|
||||
if !push_usize(out, id) { ret false; }
|
||||
ret push_out(out, ">");
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
if stack.len > 32 {
|
||||
ret push_out(out, "...");
|
||||
}
|
||||
match Vec.push(stack, &id as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
|
||||
var n = node(self, id);
|
||||
if n == null {
|
||||
if !push_out(out, "none") { ret false; }
|
||||
var popped: usize = 0;
|
||||
match Vec.pop(stack, &popped as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
var ok = true;
|
||||
match n.node {
|
||||
.Error => { ok = push_out(out, "error"); },
|
||||
.Never => { ok = push_out(out, "never"); },
|
||||
.Any => { ok = push_out(out, "any"); },
|
||||
.Unit => { ok = push_out(out, "unit"); },
|
||||
.Infer => { ok = push_out(out, "infer"); },
|
||||
.Bool => { ok = push_out(out, "bool"); },
|
||||
.Forall => { ok = push_out(out, "quant"); },
|
||||
.Exists => { ok = push_out(out, "quant"); },
|
||||
.Int(x) => {
|
||||
if x.bits == 0 {
|
||||
ok = push_out(out, "comptime_int");
|
||||
} else {
|
||||
if !push_out(out, if x.is_signed { "i" } else { "u" }) { ok = false; }
|
||||
if ok && !push_usize(out, x.bits) { ok = false; }
|
||||
}
|
||||
},
|
||||
.Float(x) => {
|
||||
if x.bits == 0 {
|
||||
ok = push_out(out, "comptime_float");
|
||||
} else {
|
||||
if !push_out(out, "f") { ok = false; }
|
||||
if ok && !push_usize(out, x.bits) { ok = false; }
|
||||
}
|
||||
},
|
||||
.Ptr(x) => {
|
||||
if !push_out(out, "*") { ok = false; }
|
||||
if ok { ok = dump_rec(self, x.pointee, out, stack); }
|
||||
},
|
||||
.Array(x) => {
|
||||
if !push_out(out, "[") { ok = false; }
|
||||
if ok && !push_usize(out, x.len) { ok = false; }
|
||||
if ok && !push_out(out, "]") { ok = false; }
|
||||
if ok { ok = dump_rec(self, x.element, out, stack); }
|
||||
},
|
||||
.Struct(x) => { ok = dump_agg(self, "struct", x, out, stack); },
|
||||
.Union(x) => { ok = dump_agg(self, "union", x, out, stack); },
|
||||
.Enum(x) => { ok = dump_agg(self, "enum", x, out, stack); },
|
||||
.Shape(x) => { ok = dump_agg(self, "shape", x, out, stack); },
|
||||
.Fn(x) => {
|
||||
if !push_out(out, "fn(") { ok = false; }
|
||||
var j: usize = 0;
|
||||
while ok && j < x.params.len {
|
||||
if j != 0 && !push_out(out, ",") { ok = false; }
|
||||
if ok { ok = dump_rec(self, usize_at(x.params, j), out, stack); }
|
||||
j = j + 1;
|
||||
}
|
||||
if ok && !push_out(out, ")") { ok = false; }
|
||||
if ok { ok = dump_rec(self, x.ret_type, out, stack); }
|
||||
},
|
||||
}
|
||||
|
||||
var dropped: usize = 0;
|
||||
match Vec.pop(stack, &dropped as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {},
|
||||
}
|
||||
ret ok;
|
||||
}
|
||||
|
||||
fn dump_agg(self: *TypeTable, kw: []u8, agg: Agg, out: *ByteBuffer, stack: *Vec): bool {
|
||||
if !push_out(out, kw) { ret false; }
|
||||
if !push_out(out, "{") { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < agg.variants.len {
|
||||
if i != 0 && !push_out(out, ", ") { ret false; }
|
||||
var m = member_at(&agg.variants, i);
|
||||
if m == null { ret false; }
|
||||
if m.name.len == 0 {
|
||||
if !push_out(out, "?") { ret false; }
|
||||
} else {
|
||||
if !push_out(out, ByteView.as_slice(m.name)) { ret false; }
|
||||
}
|
||||
if !push_out(out, ":") { ret false; }
|
||||
if !dump_rec(self, m.type_id, out, stack) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret push_out(out, "}");
|
||||
}
|
||||
|
||||
fn pure_dump(self: *TypeTable, id: usize, out: *ByteBuffer): bool {
|
||||
var stack = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
defer { Vec.drop(&stack); }
|
||||
ret dump_rec(self, id, out, &stack);
|
||||
}
|
||||
|
||||
fn pure_dump_text(self: *TypeTable, id: usize): ByteBuffer {
|
||||
var out = ByteBuffer.new();
|
||||
if !pure_dump(self, id, &out) {
|
||||
ByteBuffer.clear(&out);
|
||||
}
|
||||
ret out;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn test_intern_simple() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
var i32a = make_int(&tt, 32, true);
|
||||
var i32b = make_int(&tt, 32, true);
|
||||
var u32 = make_int(&tt, 32, false);
|
||||
@assert(i32a != 0 && i32a == i32b, "i32 interned");
|
||||
@assert(u32 != i32a, "u32 distinct");
|
||||
|
||||
var p1 = make_ptr(&tt, i32a);
|
||||
var p2 = make_ptr(&tt, i32a);
|
||||
@assert(p1 == p2, "ptr interned");
|
||||
@assert(make_ptr(&tt, u32) != p1, "ptr pointee distinct");
|
||||
|
||||
var a1 = make_array(&tt, i32a, 3);
|
||||
var a2 = make_array(&tt, i32a, 3);
|
||||
var a3 = make_array(&tt, i32a, 4);
|
||||
@assert(a1 == a2 && a1 != a3, "array interned");
|
||||
|
||||
var params = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
match Vec.push(¶ms, &i32a as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push param"); },
|
||||
}
|
||||
var f1 = make_fn(&tt, u32, params);
|
||||
var params2 = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
match Vec.push(¶ms2, &i32a as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push param"); },
|
||||
}
|
||||
var f2 = make_fn(&tt, u32, params2);
|
||||
@assert(f1 == f2, "fn interned");
|
||||
}
|
||||
|
||||
fn test_agg_intern_and_member() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
var u8t = make_int(&tt, 8, false);
|
||||
var i32t = make_int(&tt, 32, true);
|
||||
|
||||
var m1 = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&m1, "a", u8t);
|
||||
push_member(&m1, "b", i32t);
|
||||
|
||||
var s1 = make_agg(&tt, .Struct, .{
|
||||
.mode = .Auto,
|
||||
.fixed_align_bits = 0,
|
||||
.tag_type = 0,
|
||||
.variants = m1,
|
||||
});
|
||||
|
||||
var m2 = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&m2, "a", u8t);
|
||||
push_member(&m2, "b", i32t);
|
||||
var s2 = make_agg(&tt, .Struct, .{
|
||||
.mode = .Auto,
|
||||
.fixed_align_bits = 0,
|
||||
.tag_type = 0,
|
||||
.variants = m2,
|
||||
});
|
||||
@assert(s1 != 0, "s1 nonzero");
|
||||
@assert(s2 != 0, "s2 nonzero");
|
||||
@assert(pure_dump_str(&tt, s1, "struct{a:u8, b:i32}"), "s1 shape");
|
||||
@assert(pure_dump_str(&tt, s2, "struct{a:u8, b:i32}"), "s2 shape");
|
||||
@assert(s1 == s2, "struct interned");
|
||||
|
||||
var idx: usize = 0;
|
||||
@assert(agg_member_idx(&tt, s1, ByteView.from_slice("b"), &idx), "find b");
|
||||
@assert(idx == 1, "b index");
|
||||
@assert(!agg_member_idx(&tt, s1, ByteView.from_slice("z"), &idx), "missing z");
|
||||
}
|
||||
|
||||
fn test_subtype() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
var i8t = make_int(&tt, 8, true);
|
||||
var i32t = make_int(&tt, 32, true);
|
||||
var u32t = make_int(&tt, 32, false);
|
||||
var u64t = make_int(&tt, 64, false);
|
||||
var ci = make_int(&tt, 0, true);
|
||||
var never = make_never(&tt);
|
||||
var anyt = make_any(&tt);
|
||||
var f32t = make_float(&tt, 32);
|
||||
var f64t = make_float(&tt, 64);
|
||||
|
||||
@assert(is_subtype(&tt, i8t, i32t), "i8 <= i32");
|
||||
@assert(!is_subtype(&tt, i32t, i8t), "i32 !<= i8");
|
||||
@assert(!is_subtype(&tt, i32t, u32t), "i32 !<= u32");
|
||||
@assert(is_subtype(&tt, u32t, u64t), "u32 <= u64");
|
||||
@assert(!is_subtype(&tt, u32t, i32t), "u32 !<= i32");
|
||||
@assert(is_subtype(&tt, u32t, make_int(&tt, 64, true)), "u32 <= i64");
|
||||
@assert(is_subtype(&tt, ci, i32t), "comptime_int <= any int");
|
||||
@assert(is_subtype(&tt, never, i32t), "never <= all");
|
||||
@assert(is_subtype(&tt, i32t, anyt), "all <= any");
|
||||
@assert(is_subtype(&tt, f32t, f64t), "f32 <= f64");
|
||||
@assert(!is_subtype(&tt, f64t, f32t), "f64 !<= f32");
|
||||
}
|
||||
|
||||
fn test_dump() {
|
||||
var tt: TypeTable = undefined;
|
||||
match new() {
|
||||
.Ok(v) => { tt = v; },
|
||||
.Err(error) => { @assert(false, "type table new failed"); },
|
||||
}
|
||||
defer { drop(&tt); }
|
||||
|
||||
@assert(pure_dump_str(&tt, make_int(&tt, 32, true), "i32"), "dump i32");
|
||||
@assert(pure_dump_str(&tt, make_int(&tt, 0, true), "comptime_int"), "dump comptime_int");
|
||||
@assert(pure_dump_str(&tt, make_float(&tt, 64), "f64"), "dump f64");
|
||||
@assert(pure_dump_str(&tt, make_ptr(&tt, 0), "*none"), "dump ptr none");
|
||||
|
||||
var u8t = make_int(&tt, 8, false);
|
||||
@assert(pure_dump_str(&tt, make_array(&tt, u8t, 3), "[3]u8"), "dump array");
|
||||
|
||||
var params = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
match Vec.push(¶ms, &u8t as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "push param"); },
|
||||
}
|
||||
var fn_t = make_fn(&tt, 0, params);
|
||||
@assert(pure_dump_str(&tt, fn_t, "fn(u8)none"), "dump fn");
|
||||
|
||||
var members = Vec.blank(@sizeof(Member), @alignof(Member));
|
||||
push_member(&members, "x", u8t);
|
||||
var st = make_agg(&tt, .Struct, .{
|
||||
.mode = .Auto,
|
||||
.fixed_align_bits = 0,
|
||||
.tag_type = 0,
|
||||
.variants = members,
|
||||
});
|
||||
@assert(pure_dump_str(&tt, st, "struct{x:u8}"), "dump struct");
|
||||
}
|
||||
|
||||
fn pure_dump_str(tt: *TypeTable, id: usize, expected: []u8): bool {
|
||||
var out = pure_dump_text(tt, id);
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
if got.len != expected.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
if got.ptr[i] != expected.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_intern_simple();
|
||||
test_agg_intern_and_member();
|
||||
test_subtype();
|
||||
test_dump();
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 类型擦除的搜索与稳定归并排序。元素由定长字节表示,比较回调负责解释元素和键;
|
||||
// context 只借用,模块不持有元素、键或上下文。所有尺寸错误都在写入前返回。
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .checked_mul } = @import("memory.spl");
|
||||
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
|
||||
|
||||
type SearchResult = enum {
|
||||
Found: usize,
|
||||
NotFound,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn validate_range(data: *u8, len: usize, elem_size: usize): Status {
|
||||
if elem_size == 0 { ret .Err(make(.InvalidArgument)); }
|
||||
match checked_mul(len, elem_size) {
|
||||
.Ok(bytes) => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if len != 0 && data == null { ret .Err(make(.InvalidArgument)); }
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 返回首个相等元素。空范围不读取 data、key 或调用 compare。
|
||||
fn linear_search(data: *u8, len: usize, elem_size: usize, key: *u8,
|
||||
context: *u8, compare: fn(*u8, *u8, *u8): i32): SearchResult {
|
||||
match validate_range(data, len, elem_size) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if len == 0 { ret .NotFound; }
|
||||
if key == null { ret .Err(make(.InvalidArgument)); }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < len {
|
||||
if compare(data + i * elem_size, key, context) == 0 {
|
||||
ret .Found(i);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
// data 必须按 compare 升序排列。存在重复值时返回首个相等元素。
|
||||
fn binary_search(data: *u8, len: usize, elem_size: usize, key: *u8,
|
||||
context: *u8, compare: fn(*u8, *u8, *u8): i32): SearchResult {
|
||||
match validate_range(data, len, elem_size) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if len == 0 { ret .NotFound; }
|
||||
if key == null { ret .Err(make(.InvalidArgument)); }
|
||||
|
||||
var low: usize = 0;
|
||||
var high = len;
|
||||
while low < high {
|
||||
var mid = low + (high - low) / 2;
|
||||
if compare(data + mid * elem_size, key, context) < 0 {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
if low < len && compare(data + low * elem_size, key, context) == 0 {
|
||||
ret .Found(low);
|
||||
}
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
fn min_size(a: usize, b: usize): usize {
|
||||
if a < b { ret a; }
|
||||
ret b;
|
||||
}
|
||||
|
||||
fn merge_run(dst: *u8, src: *u8, elem_size: usize,
|
||||
begin: usize, middle: usize, end: usize,
|
||||
context: *u8, compare: fn(*u8, *u8, *u8): i32) {
|
||||
var left = begin;
|
||||
var right = middle;
|
||||
var output = begin;
|
||||
while left < middle && right < end {
|
||||
var left_ptr = src + left * elem_size;
|
||||
var right_ptr = src + right * elem_size;
|
||||
// 相等时先取左侧元素,由此保证稳定性。
|
||||
if compare(left_ptr, right_ptr, context) <= 0 {
|
||||
copy((dst + output * elem_size) as *any, left_ptr as *any, elem_size);
|
||||
left = left + 1;
|
||||
} else {
|
||||
copy((dst + output * elem_size) as *any, right_ptr as *any, elem_size);
|
||||
right = right + 1;
|
||||
}
|
||||
output = output + 1;
|
||||
}
|
||||
while left < middle {
|
||||
copy((dst + output * elem_size) as *any,
|
||||
(src + left * elem_size) as *any, elem_size);
|
||||
left = left + 1;
|
||||
output = output + 1;
|
||||
}
|
||||
while right < end {
|
||||
copy((dst + output * elem_size) as *any,
|
||||
(src + right * elem_size) as *any, elem_size);
|
||||
right = right + 1;
|
||||
output = output + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 自底向上的稳定归并排序。分配失败及参数错误不改变输入。
|
||||
fn stable_sort(data: *u8, len: usize, elem_size: usize,
|
||||
context: *u8, compare: fn(*u8, *u8, *u8): i32): Status {
|
||||
var bytes: usize = 0;
|
||||
match validate_range(data, len, elem_size) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match checked_mul(len, elem_size) {
|
||||
.Ok(value) => { bytes = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if len < 2 { ret .Ok; }
|
||||
|
||||
var scratch = alloc(bytes) as *u8;
|
||||
if scratch == null { ret .Err(make(.Overflow)); }
|
||||
defer { free(scratch as *any); }
|
||||
|
||||
var source = data;
|
||||
var destination = scratch;
|
||||
var width: usize = 1;
|
||||
while width < len {
|
||||
var begin: usize = 0;
|
||||
while begin < len {
|
||||
var left_width = min_size(width, len - begin);
|
||||
var middle = begin + left_width;
|
||||
var remaining = len - middle;
|
||||
var right_width = min_size(width, remaining);
|
||||
var end = middle + right_width;
|
||||
merge_run(destination, source, elem_size, begin, middle, end,
|
||||
context, compare);
|
||||
begin = end;
|
||||
}
|
||||
var swap = source;
|
||||
source = destination;
|
||||
destination = swap;
|
||||
if width > len / 2 {
|
||||
width = len;
|
||||
} else {
|
||||
width = width * 2;
|
||||
}
|
||||
}
|
||||
|
||||
if source != data {
|
||||
copy(data as *any, source as *any, bytes);
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
type Item = struct {
|
||||
key: i32,
|
||||
sequence: i32,
|
||||
}
|
||||
|
||||
fn compare_i32(a: *u8, b: *u8, context: *u8): i32 {
|
||||
var av = (a as *i32).*;
|
||||
var bv = (b as *i32).*;
|
||||
if av < bv { ret -1; }
|
||||
if av > bv { ret 1; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn compare_item(a: *u8, b: *u8, context: *u8): i32 {
|
||||
var av = (a as *Item).*.key;
|
||||
var bv = (b as *Item).*.key;
|
||||
var direction: i32 = 1;
|
||||
if context != null { direction = (context as *i32).*; }
|
||||
if av < bv { ret -direction; }
|
||||
if av > bv { ret direction; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected algorithm error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_found(result: SearchResult, expected: usize) {
|
||||
match result {
|
||||
.Found(index) => { @assert(index == expected, "search index"); },
|
||||
.NotFound => { @assert(false, "expected search match"); },
|
||||
.Err(error) => { @assert(false, "unexpected search error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_missing(result: SearchResult) {
|
||||
match result {
|
||||
.Found(index) => { @assert(false, "unexpected search match"); },
|
||||
.NotFound => {},
|
||||
.Err(error) => { @assert(false, "unexpected search error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_search_error(result: SearchResult, overflow: bool) {
|
||||
match result {
|
||||
.Found(index) => { @assert(false, "expected search error"); },
|
||||
.NotFound => { @assert(false, "expected search error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => { @assert(overflow); },
|
||||
.InvalidArgument => { @assert(!overflow); },
|
||||
_ => { @assert(false, "unexpected search error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_search() {
|
||||
var values: [8]i32 = .[1, 2, 2, 2, 5, 8, 13, 21];
|
||||
var two: i32 = 2;
|
||||
var seven: i32 = 7;
|
||||
expect_found(linear_search(&values[0] as *u8, 8, @sizeof(i32),
|
||||
&two as *u8, null, compare_i32), 1);
|
||||
expect_found(binary_search(&values[0] as *u8, 8, @sizeof(i32),
|
||||
&two as *u8, null, compare_i32), 1);
|
||||
expect_missing(linear_search(&values[0] as *u8, 8, @sizeof(i32),
|
||||
&seven as *u8, null, compare_i32));
|
||||
expect_missing(binary_search(&values[0] as *u8, 8, @sizeof(i32),
|
||||
&seven as *u8, null, compare_i32));
|
||||
|
||||
expect_missing(linear_search(null, 0, @sizeof(i32), null, null, compare_i32));
|
||||
expect_missing(binary_search(null, 0, @sizeof(i32), null, null, compare_i32));
|
||||
expect_search_error(linear_search(&values[0] as *u8, 8, 0,
|
||||
&two as *u8, null, compare_i32), false);
|
||||
expect_search_error(binary_search(null, 1, @sizeof(i32),
|
||||
&two as *u8, null, compare_i32), false);
|
||||
var huge = (~(0 as usize)) / @sizeof(i32) + 1;
|
||||
expect_search_error(linear_search(&values[0] as *u8, huge, @sizeof(i32),
|
||||
&two as *u8, null, compare_i32), true);
|
||||
}
|
||||
|
||||
fn test_stable_sort() {
|
||||
var items: [8]Item = .[
|
||||
.{ .key = 3, .sequence = 0 },
|
||||
.{ .key = 1, .sequence = 1 },
|
||||
.{ .key = 2, .sequence = 2 },
|
||||
.{ .key = 1, .sequence = 3 },
|
||||
.{ .key = 3, .sequence = 4 },
|
||||
.{ .key = 2, .sequence = 5 },
|
||||
.{ .key = 1, .sequence = 6 },
|
||||
.{ .key = 2, .sequence = 7 },
|
||||
];
|
||||
var ascending: i32 = 1;
|
||||
expect_ok(stable_sort(&items[0] as *u8, 8, @sizeof(Item),
|
||||
&ascending as *u8, compare_item));
|
||||
var expected_keys: [8]i32 = .[1, 1, 1, 2, 2, 2, 3, 3];
|
||||
var expected_sequence: [8]i32 = .[1, 3, 6, 2, 5, 7, 0, 4];
|
||||
var i: usize = 0;
|
||||
while i < 8 {
|
||||
@assert(items[i].key == expected_keys[i], "sorted key");
|
||||
@assert(items[i].sequence == expected_sequence[i], "stable equal order");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
var descending: i32 = -1;
|
||||
expect_ok(stable_sort(&items[0] as *u8, 8, @sizeof(Item),
|
||||
&descending as *u8, compare_item));
|
||||
i = 1;
|
||||
while i < 8 {
|
||||
@assert(items[i - 1].key >= items[i].key, "context controls order");
|
||||
if items[i - 1].key == items[i].key {
|
||||
@assert(items[i - 1].sequence < items[i].sequence,
|
||||
"second stable sort preserves group order");
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
expect_ok(stable_sort(null, 0, @sizeof(Item), null, compare_item));
|
||||
expect_ok(stable_sort(&items[0] as *u8, 1, @sizeof(Item), null, compare_item));
|
||||
|
||||
// 非二次幂长度覆盖末尾不足一个完整归并段的路径。
|
||||
var odd: [9]i32 = .[9, -1, 4, 4, 0, 8, 2, 7, 3];
|
||||
var odd_expected: [9]i32 = .[-1, 0, 2, 3, 4, 4, 7, 8, 9];
|
||||
expect_ok(stable_sort(&odd[0] as *u8, 9, @sizeof(i32), null, compare_i32));
|
||||
i = 0;
|
||||
while i < 9 {
|
||||
@assert(odd[i] == odd_expected[i], "non-power-of-two length");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_sort_failures_preserve_input() {
|
||||
var values: [3]i32 = .[3, 1, 2];
|
||||
match stable_sort(&values[0] as *u8, 3, 0, null, compare_i32) {
|
||||
.Ok => { @assert(false, "zero element size"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {},
|
||||
_ => { @assert(false); },
|
||||
}
|
||||
},
|
||||
}
|
||||
@assert(values[0] == 3 && values[1] == 1 && values[2] == 2,
|
||||
"validation failure preserves input");
|
||||
|
||||
var huge = (~(0 as usize)) / @sizeof(i32) + 1;
|
||||
match stable_sort(&values[0] as *u8, huge, @sizeof(i32), null, compare_i32) {
|
||||
.Ok => { @assert(false, "size overflow"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false); },
|
||||
}
|
||||
},
|
||||
}
|
||||
@assert(values[0] == 3 && values[1] == 1 && values[2] == 2,
|
||||
"overflow preserves input");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_search();
|
||||
test_stable_sort();
|
||||
test_sort_failures_preserve_input();
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 分块 bump 分配器:按块分配、块内顺序推进,只在 drop 时整体释放。
|
||||
// 适合 AST/IR 这类生命周期一致的批量小对象;不支持单独 free。
|
||||
// 分配失败通过 ArenaResult 区分原因:size==0/非法对齐/内存不足/地址溢出。
|
||||
// 失败的 alloc 不改变已有分配。
|
||||
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .Error, .Kind, .Status } = @import("error.spl");
|
||||
type .{ .checked_add, .align_up } = @import("memory.spl");
|
||||
type Mem = @import("../sys/mem.spl");
|
||||
|
||||
type Chunk = struct {
|
||||
base: *u8,
|
||||
size: usize,
|
||||
used: usize,
|
||||
}
|
||||
|
||||
type ArenaResult = enum {
|
||||
Ok: *u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type Arena = @self;
|
||||
|
||||
chunk_size: usize,
|
||||
chunks: Vec, // 元素类型 Chunk
|
||||
current: usize,
|
||||
|
||||
fn new(chunk_size: usize): Arena {
|
||||
var cs = chunk_size;
|
||||
if cs == 0 { cs = 4096; }
|
||||
ret Arena {
|
||||
.chunk_size = cs,
|
||||
.chunks = Vec.blank(@sizeof(Chunk), 8),
|
||||
.current = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn chunk_at(self: *Arena, index: usize): *Chunk {
|
||||
match Vec.get(&self.chunks, index) {
|
||||
.Ok(ptr) => { ret ptr as *Chunk; },
|
||||
// 越界/无效 Arena 统一返回 null;调用方负责判空,无资源需要释放。
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn push_chunk(self: *Arena, size: usize): Status {
|
||||
var base = Mem.alloc(size) as *u8;
|
||||
if base == null { ret .Err(Error.make(.Overflow)); }
|
||||
var c = Chunk { .base = base, .size = size, .used = 0 };
|
||||
match Vec.push(&self.chunks, &c as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Mem.free(base as *any); ret .Err(error); },
|
||||
}
|
||||
self.current = self.chunks.len - 1;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 ArenaResult.Err。
|
||||
fn alloc(self: *Arena, size: usize, align: usize): ArenaResult {
|
||||
if size == 0 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if align == 0 { align = 1; }
|
||||
|
||||
while true {
|
||||
if self.current >= self.chunks.len {
|
||||
var want = self.chunk_size;
|
||||
var need: usize = 0;
|
||||
match checked_add(size, align) {
|
||||
.Ok(value) => { need = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if need > want { want = need; }
|
||||
match push_chunk(self, want) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
var c = chunk_at(self, self.current);
|
||||
if c == null { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var addr = c.base as usize;
|
||||
var used_addr: usize = 0;
|
||||
match checked_add(addr, c.used) {
|
||||
.Ok(value) => { used_addr = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var aligned: usize = 0;
|
||||
match align_up(used_addr, align) {
|
||||
.Ok(value) => { aligned = value; },
|
||||
.Err(error) => { ret .Err(error); }, // 非二次幂对齐或地址溢出
|
||||
}
|
||||
var offset = aligned - addr;
|
||||
if offset <= c.size && size <= c.size - offset {
|
||||
c.used = offset + size;
|
||||
ret .Ok(aligned as *u8);
|
||||
}
|
||||
self.current = self.current + 1;
|
||||
}
|
||||
ret .Err(Error.make(.OutOfRange)); // 不可达:循环内所有路径均已返回
|
||||
}
|
||||
|
||||
fn chunk_count(self: Arena): usize {
|
||||
ret self.chunks.len;
|
||||
}
|
||||
|
||||
// 复用已分配的块,不释放内存。
|
||||
fn reset(self: *Arena) {
|
||||
var i: usize = 0;
|
||||
while i < self.chunks.len {
|
||||
var c = chunk_at(self, i);
|
||||
if c != null { c.used = 0; }
|
||||
i = i + 1;
|
||||
}
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
fn drop(self: *Arena) {
|
||||
var i: usize = 0;
|
||||
while i < self.chunks.len {
|
||||
var c = chunk_at(self, i);
|
||||
if c != null { Mem.free(c.base as *any); }
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.chunks);
|
||||
self.current = 0;
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn is_aligned(p: *u8, align: usize): bool {
|
||||
ret ((p as usize) & (align - 1)) == 0;
|
||||
}
|
||||
|
||||
fn alloc_ok(a: *Arena, size: usize, align: usize): *u8 {
|
||||
match alloc(a, size, align) {
|
||||
.Ok(ptr) => { ret ptr; },
|
||||
.Err(error) => { @assert(false, "unexpected arena alloc error"); },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn expect_alloc_error(result: ArenaResult, expected: Kind) {
|
||||
match result {
|
||||
.Ok(ptr) => { @assert(false, "expected arena alloc error"); },
|
||||
.Err(error) => {
|
||||
var want: Kind = expected;
|
||||
@assert(@enum2int(error.kind) == @enum2int(want), "arena alloc error kind");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_alloc_and_alignment() {
|
||||
var a = new(64);
|
||||
@assert(chunk_count(a) == 0, "lazy");
|
||||
|
||||
var p1 = alloc_ok(&a, 1, 1);
|
||||
var p8 = alloc_ok(&a, 16, 8);
|
||||
var p16 = alloc_ok(&a, 1, 16);
|
||||
@assert(p1 != null && p8 != null && p16 != null, "alloc");
|
||||
@assert(p1 != p8 && p8 != p16 && p1 != p16, "distinct");
|
||||
@assert(is_aligned(p8, 8), "align 8");
|
||||
@assert(is_aligned(p16, 16), "align 16");
|
||||
|
||||
p1[0] = 7;
|
||||
@assert(p1[0] == 7, "writable");
|
||||
@assert(p8[0] == p8[0], "no crash");
|
||||
|
||||
@assert(chunk_count(a) == 1, "one chunk");
|
||||
reset(&a);
|
||||
@assert(chunk_count(a) == 1, "reset keeps chunks");
|
||||
var q = alloc_ok(&a, 1, 1);
|
||||
@assert(q == p1, "reset reuses first chunk");
|
||||
|
||||
// drop 后还有断言,故保持手动释放,不改为 defer。
|
||||
drop(&a);
|
||||
@assert(chunk_count(a) == 0, "drop empties");
|
||||
}
|
||||
|
||||
fn test_growth() {
|
||||
var a = new(64);
|
||||
defer { drop(&a); }
|
||||
var i: i32 = 0;
|
||||
while i < 100 {
|
||||
var p = alloc_ok(&a, 16, 8);
|
||||
@assert(p != null, "grow alloc");
|
||||
@assert(is_aligned(p, 8), "grow align");
|
||||
p[0] = i as u8;
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(chunk_count(a) > 1, "grew multiple chunks");
|
||||
|
||||
// 超过块大小的单次分配也能成功(新块按需放大)。
|
||||
var big = alloc_ok(&a, 1024, 16);
|
||||
@assert(big != null && is_aligned(big, 16), "oversized alloc");
|
||||
}
|
||||
|
||||
fn test_failures() {
|
||||
var a = new(64);
|
||||
defer { drop(&a); }
|
||||
expect_alloc_error(alloc(&a, 0, 1), .InvalidArgument);
|
||||
var d = alloc_ok(&a, 8, 0);
|
||||
@assert(d != null && is_aligned(d, 1), "align 0 defaults to 1");
|
||||
expect_alloc_error(alloc(&a, 8, 3), .InvalidAlignment);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_alloc_and_alignment();
|
||||
test_growth();
|
||||
test_failures();
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// ASCII 字符工具。输入和输出都是单个字节,不解释 UTF-8 或本地化规则。
|
||||
|
||||
type .{ .Error, .make } = @import("error.spl");
|
||||
|
||||
type DigitResult = enum {
|
||||
Ok: u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn is_ascii(c: u8): bool {
|
||||
ret c <= 127;
|
||||
}
|
||||
|
||||
fn is_control(c: u8): bool {
|
||||
ret c <= 31 || c == 127;
|
||||
}
|
||||
|
||||
fn is_space(c: u8): bool {
|
||||
ret c == ' ' || (c >= '\t' && c <= '\r');
|
||||
}
|
||||
|
||||
fn is_blank(c: u8): bool {
|
||||
ret c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
fn is_digit(c: u8): bool {
|
||||
ret c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
fn is_lower(c: u8): bool {
|
||||
ret c >= 'a' && c <= 'z';
|
||||
}
|
||||
|
||||
fn is_upper(c: u8): bool {
|
||||
ret c >= 'A' && c <= 'Z';
|
||||
}
|
||||
|
||||
fn is_alpha(c: u8): bool {
|
||||
ret is_lower(c) || is_upper(c);
|
||||
}
|
||||
|
||||
fn is_alnum(c: u8): bool {
|
||||
ret is_alpha(c) || is_digit(c);
|
||||
}
|
||||
|
||||
fn is_hex_digit(c: u8): bool {
|
||||
ret is_digit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
|
||||
fn is_print(c: u8): bool {
|
||||
ret c >= ' ' && c <= '~';
|
||||
}
|
||||
|
||||
fn is_graph(c: u8): bool {
|
||||
ret c >= '!' && c <= '~';
|
||||
}
|
||||
|
||||
fn is_punct(c: u8): bool {
|
||||
ret is_graph(c) && !is_alnum(c);
|
||||
}
|
||||
|
||||
fn to_lower(c: u8): u8 {
|
||||
if is_upper(c) { ret c + ('a' - 'A'); }
|
||||
ret c;
|
||||
}
|
||||
|
||||
fn to_upper(c: u8): u8 {
|
||||
if is_lower(c) { ret c - ('a' - 'A'); }
|
||||
ret c;
|
||||
}
|
||||
|
||||
// radix 必须位于 2..36。大小写字母均表示 10..35。
|
||||
fn digit_value(c: u8, radix: u8): DigitResult {
|
||||
if radix < 2 || radix > 36 {
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
|
||||
var value: u8 = 0;
|
||||
if is_digit(c) {
|
||||
value = c - '0';
|
||||
} else if is_upper(c) {
|
||||
value = c - 'A' + 10;
|
||||
} else if is_lower(c) {
|
||||
value = c - 'a' + 10;
|
||||
} else {
|
||||
ret .Err(make(.InvalidDigit));
|
||||
}
|
||||
|
||||
if value >= radix { ret .Err(make(.InvalidDigit)); }
|
||||
ret .Ok(value);
|
||||
}
|
||||
|
||||
// value 必须位于 0..35。uppercase 只影响 10..35 的字母形式。
|
||||
fn digit_char(value: u8, uppercase: bool): DigitResult {
|
||||
if value >= 36 { ret .Err(make(.OutOfRange)); }
|
||||
if value < 10 { ret .Ok(value + '0'); }
|
||||
if uppercase { ret .Ok(value - 10 + 'A'); }
|
||||
ret .Ok(value - 10 + 'a');
|
||||
}
|
||||
|
||||
fn expect_digit(result: DigitResult, expected: u8) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(value == expected, "digit value"); },
|
||||
.Err(error) => { @assert(false, "unexpected digit error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_error(result: DigitResult, expected_invalid_argument: bool) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected digit error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {
|
||||
@assert(expected_invalid_argument, "unexpected invalid radix");
|
||||
},
|
||||
.InvalidDigit => {
|
||||
@assert(!expected_invalid_argument, "unexpected invalid digit");
|
||||
},
|
||||
_ => { @assert(false, "unexpected digit error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_range_error(result: DigitResult) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected digit range error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => {},
|
||||
_ => { @assert(false, "unexpected digit range error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_all_bytes() {
|
||||
var i: usize = 0;
|
||||
while i < 256 {
|
||||
var c = i as u8;
|
||||
@assert(is_ascii(c) == (i <= 127), "ASCII boundary");
|
||||
@assert(is_control(c) == (i <= 31 || i == 127), "control boundary");
|
||||
@assert(is_print(c) == (c >= ' ' && c <= '~'), "print boundary");
|
||||
@assert(is_graph(c) == (c >= '!' && c <= '~'), "graph boundary");
|
||||
@assert(is_blank(c) == (c == '\t' || c == ' '), "blank set");
|
||||
@assert(is_space(c) == (c == '\t' || c == '\n' || c == '\x0B' || c == '\x0C' ||
|
||||
c == '\r' || c == ' '),
|
||||
"space set");
|
||||
@assert(is_digit(c) == (c >= '0' && c <= '9'), "digit set");
|
||||
@assert(is_upper(c) == (c >= 'A' && c <= 'Z'), "upper set");
|
||||
@assert(is_lower(c) == (c >= 'a' && c <= 'z'), "lower set");
|
||||
@assert(is_hex_digit(c) == ((c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'F') ||
|
||||
(c >= 'a' && c <= 'f')),
|
||||
"hex digit set");
|
||||
@assert(is_alpha(c) == (is_upper(c) || is_lower(c)), "alpha relation");
|
||||
@assert(is_alnum(c) == (is_alpha(c) || is_digit(c)), "alnum relation");
|
||||
@assert(is_punct(c) == (is_graph(c) && !is_alnum(c)), "punct relation");
|
||||
|
||||
if is_upper(c) {
|
||||
@assert(to_lower(c) == c + ('a' - 'A'), "upper to lower");
|
||||
@assert(to_upper(to_lower(c)) == c, "case round trip");
|
||||
} else if is_lower(c) {
|
||||
@assert(to_upper(c) == c - ('a' - 'A'), "lower to upper");
|
||||
@assert(to_lower(to_upper(c)) == c, "case round trip");
|
||||
} else {
|
||||
@assert(to_lower(c) == c, "lower preserves non-letter");
|
||||
@assert(to_upper(c) == c, "upper preserves non-letter");
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_digits() {
|
||||
var radix: usize = 2;
|
||||
while radix <= 36 {
|
||||
var value: usize = 0;
|
||||
while value < radix {
|
||||
var lower: u8 = 0;
|
||||
match digit_char(value as u8, false) {
|
||||
.Ok(c) => { lower = c; },
|
||||
.Err(error) => { @assert(false, "lower digit generation"); },
|
||||
}
|
||||
expect_digit(digit_value(lower, radix as u8), value as u8);
|
||||
|
||||
var upper: u8 = 0;
|
||||
match digit_char(value as u8, true) {
|
||||
.Ok(c) => { upper = c; },
|
||||
.Err(error) => { @assert(false, "upper digit generation"); },
|
||||
}
|
||||
expect_digit(digit_value(upper, radix as u8), value as u8);
|
||||
if value < 10 {
|
||||
@assert(lower == upper, "decimal digits ignore case");
|
||||
} else {
|
||||
@assert(lower == upper + ('a' - 'A'), "letter digit case distance");
|
||||
}
|
||||
value = value + 1;
|
||||
}
|
||||
radix = radix + 1;
|
||||
}
|
||||
|
||||
expect_error(digit_value('0', 0), true);
|
||||
expect_error(digit_value('0', 1), true);
|
||||
expect_error(digit_value('0', 37), true);
|
||||
expect_error(digit_value('/', 10), false);
|
||||
expect_error(digit_value(':', 10), false);
|
||||
expect_error(digit_value('2', 2), false);
|
||||
expect_error(digit_value('g', 16), false);
|
||||
expect_range_error(digit_char(36, false));
|
||||
expect_range_error(digit_char(255, true));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_all_bytes();
|
||||
test_digits();
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 拥有字节存储的动态缓冲区。普通赋值会复制所有权,转移必须使用 take。
|
||||
// 借用切片和 z_ptr 返回的指针在下一次可能扩容的操作或 drop 后失效。
|
||||
|
||||
type .{ .Error, .Status } = @import("error.spl");
|
||||
type .{ .checked_add, .move } = @import("memory.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
|
||||
type ByteBuffer = @self;
|
||||
|
||||
storage: Vec,
|
||||
|
||||
type PtrResult = enum {
|
||||
Ok: *u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn new(): ByteBuffer {
|
||||
match Vec.new(1, 1) {
|
||||
.Ok(value) => { ret ByteBuffer { .storage = value }; },
|
||||
.Err(error) => { @assert(false, "fixed byte layout is valid"); },
|
||||
}
|
||||
ret ByteBuffer { .storage = Vec.blank(1, 1) };
|
||||
}
|
||||
|
||||
fn with_capacity(cap: usize): ByteBuffer {
|
||||
var b = new();
|
||||
match reserve(&b, cap) { .Ok => {}, .Err(error) => {} }
|
||||
ret b;
|
||||
}
|
||||
|
||||
fn size(self: ByteBuffer): usize {
|
||||
ret self.storage.len;
|
||||
}
|
||||
|
||||
fn capacity(self: ByteBuffer): usize {
|
||||
ret self.storage.cap;
|
||||
}
|
||||
|
||||
fn is_empty(self: ByteBuffer): bool {
|
||||
ret self.storage.len == 0;
|
||||
}
|
||||
|
||||
fn as_slice(self: ByteBuffer): []u8 {
|
||||
ret .{ .ptr = self.storage.data, .len = self.storage.len };
|
||||
}
|
||||
|
||||
fn reserve(self: *ByteBuffer, capacity_needed: usize): Status {
|
||||
ret Vec.reserve(&self.storage, capacity_needed);
|
||||
}
|
||||
|
||||
fn append(self: *ByteBuffer, bytes: []u8): Status {
|
||||
ret Vec.append(&self.storage, bytes.ptr, bytes.len);
|
||||
}
|
||||
|
||||
fn append_byte(self: *ByteBuffer, byte: u8): Status {
|
||||
ret Vec.push(&self.storage, &byte);
|
||||
}
|
||||
|
||||
fn append_repeat(self: *ByteBuffer, byte: u8, count: usize): Status {
|
||||
if count == 0 { ret .Ok; }
|
||||
|
||||
var new_len: usize = 0;
|
||||
match checked_add(self.storage.len, count) {
|
||||
.Ok(value) => { new_len = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match reserve(self, new_len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
|
||||
var i = self.storage.len;
|
||||
while i < new_len {
|
||||
self.storage.data[i] = byte;
|
||||
i = i + 1;
|
||||
}
|
||||
self.storage.len = new_len;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 扩展时用 fill 初始化新增字节;缩短只改变逻辑长度。
|
||||
fn resize(self: *ByteBuffer, new_len: usize, fill: u8): Status {
|
||||
if new_len <= self.storage.len {
|
||||
self.storage.len = new_len;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
var count = new_len - self.storage.len;
|
||||
ret append_repeat(self, fill, count);
|
||||
}
|
||||
|
||||
fn clear(self: *ByteBuffer) {
|
||||
Vec.clear(&self.storage);
|
||||
}
|
||||
|
||||
// 保证 data[len] 为零并返回 data。终止零不属于逻辑内容。
|
||||
// 缓冲区内容可以包含更早的零字节,调用方决定是否适合作为 C 字符串。
|
||||
fn z_ptr(self: *ByteBuffer): PtrResult {
|
||||
var needed: usize = 0;
|
||||
match checked_add(self.storage.len, 1) {
|
||||
.Ok(value) => { needed = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match reserve(self, needed) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
self.storage.data[self.storage.len] = 0;
|
||||
ret .Ok(self.storage.data);
|
||||
}
|
||||
|
||||
// 成功时替换 out 的旧内容;失败保持 out 不变。允许 bytes 借用 out 自身。
|
||||
fn assign(out: *ByteBuffer, bytes: []u8): Status {
|
||||
if bytes.len == 0 {
|
||||
clear(out);
|
||||
ret .Ok;
|
||||
}
|
||||
if bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
|
||||
|
||||
var used = out.storage.len;
|
||||
if used != 0 {
|
||||
var base = out.storage.data as usize;
|
||||
var source = bytes.ptr as usize;
|
||||
if source >= base && source - base < used {
|
||||
var offset = source - base;
|
||||
if bytes.len > used - offset {
|
||||
ret .Err(Error.make(.OutOfRange));
|
||||
}
|
||||
match move(out.storage.data[0..bytes.len], bytes) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
out.storage.len = bytes.len;
|
||||
ret .Ok;
|
||||
}
|
||||
}
|
||||
|
||||
var fresh = new();
|
||||
match append(&fresh, bytes) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
drop(&fresh);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
drop(out);
|
||||
out.storage = Vec.take(&fresh.storage);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 深复制;允许 out 与 source 是同一对象。
|
||||
fn clone(out: *ByteBuffer, source: ByteBuffer): Status {
|
||||
ret Vec.clone(&out.storage, source.storage);
|
||||
}
|
||||
|
||||
fn take(source: *ByteBuffer): ByteBuffer {
|
||||
ret ByteBuffer { .storage = Vec.take(&source.storage) };
|
||||
}
|
||||
|
||||
fn drop(self: *ByteBuffer) {
|
||||
Vec.drop(&self.storage);
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => { @assert(false, "unexpected invalid argument"); },
|
||||
.OutOfRange => { @assert(false, "unexpected out of range"); },
|
||||
.Overflow => { @assert(false, "unexpected overflow"); },
|
||||
_ => { @assert(false, "unexpected byte buffer error"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_overflow(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected byte buffer overflow"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false, "unexpected byte buffer error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_bytes(self: ByteBuffer, expected: []u8) {
|
||||
@assert(self.storage.len == expected.len, "byte buffer length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(self.storage.data[i] == expected.ptr[i], "byte buffer content");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_z(self: *ByteBuffer) {
|
||||
var old_len = self.storage.len;
|
||||
match z_ptr(self) {
|
||||
.Ok(ptr) => {
|
||||
@assert(ptr != null, "zero terminated pointer");
|
||||
@assert(ptr[old_len] == 0, "zero terminator");
|
||||
},
|
||||
.Err(error) => { @assert(false, "zero termination"); },
|
||||
}
|
||||
@assert(self.storage.len == old_len, "terminator is outside logical content");
|
||||
}
|
||||
|
||||
fn test_append_and_resize() {
|
||||
var buffer = new();
|
||||
defer { drop(&buffer); }
|
||||
@assert(is_empty(buffer));
|
||||
@assert(size(buffer) == 0 && capacity(buffer) == 0);
|
||||
expect_z(&buffer);
|
||||
@assert(capacity(buffer) >= 1);
|
||||
|
||||
expect_ok(append(&buffer, "abc"));
|
||||
expect_ok(append_byte(&buffer, '\0'));
|
||||
expect_ok(append(&buffer, "de"));
|
||||
@assert(buffer.storage.len == 6, "append logical length");
|
||||
var embedded: [6]u8 = .[97, 98, 99, 0, 100, 101];
|
||||
expect_bytes(buffer, embedded[0..6]);
|
||||
expect_z(&buffer);
|
||||
|
||||
expect_ok(resize(&buffer, 9, 255));
|
||||
@assert(buffer.storage.len == 9, "grow logical length");
|
||||
var grown: [9]u8 = .[97, 98, 99, 0, 100, 101, 255, 255, 255];
|
||||
expect_bytes(buffer, grown[0..9]);
|
||||
expect_ok(resize(&buffer, 2, 17));
|
||||
@assert(buffer.storage.len == 2, "shrink logical length");
|
||||
expect_bytes(buffer, "ab");
|
||||
expect_ok(append_repeat(&buffer, 120, 4));
|
||||
@assert(buffer.storage.len == 6, "repeat logical length");
|
||||
expect_bytes(buffer, "abxxxx");
|
||||
|
||||
clear(&buffer);
|
||||
@assert(is_empty(buffer) && capacity(buffer) != 0, "clear retains allocation");
|
||||
expect_z(&buffer);
|
||||
}
|
||||
|
||||
fn test_growth_and_self_append() {
|
||||
var buffer = new();
|
||||
defer { drop(&buffer); }
|
||||
var i: usize = 0;
|
||||
while i < 8 {
|
||||
expect_ok(append_byte(&buffer, (65 + i) as u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(buffer.storage.cap == 8, "prepare byte buffer growth boundary");
|
||||
|
||||
var own = as_slice(buffer);
|
||||
expect_ok(append(&buffer, own));
|
||||
@assert(buffer.storage.len == 16);
|
||||
i = 0;
|
||||
while i < 16 {
|
||||
@assert(buffer.storage.data[i] == (65 + i % 8) as u8, "self append content");
|
||||
i = i + 1;
|
||||
}
|
||||
expect_z(&buffer);
|
||||
}
|
||||
|
||||
fn test_ownership_and_failures() {
|
||||
var source = new();
|
||||
defer { drop(&source); }
|
||||
expect_ok(append(&source, "owned"));
|
||||
|
||||
var copy = new();
|
||||
defer { drop(©); }
|
||||
expect_ok(clone(©, source));
|
||||
@assert(copy.storage.len == 5, "clone logical length");
|
||||
source.storage.data[0] = 79;
|
||||
expect_bytes(copy, "owned");
|
||||
|
||||
var before_self_clone = copy.storage.raw;
|
||||
expect_ok(clone(©, copy));
|
||||
@assert(copy.storage.raw != before_self_clone, "self clone replaces nested allocation");
|
||||
expect_bytes(copy, "owned");
|
||||
|
||||
var before_external_assign = copy.storage.data;
|
||||
expect_ok(assign(©, "xyz"));
|
||||
@assert(copy.storage.len == 3, "external assign logical length");
|
||||
@assert(copy.storage.data != before_external_assign, "external assign replaces data");
|
||||
expect_bytes(copy, "xyz");
|
||||
before_external_assign = copy.storage.data;
|
||||
expect_ok(assign(©, "owned"));
|
||||
@assert(copy.storage.len == 5, "second external assign logical length");
|
||||
@assert(copy.storage.data != before_external_assign, "second external assign replaces data");
|
||||
|
||||
// assign 的输入借用目标自身,提交前必须保持旧分配有效。
|
||||
var middle = as_slice(copy);
|
||||
middle.ptr = middle.ptr + 1;
|
||||
middle.len = 3;
|
||||
match assign(©, middle) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "self assign failed"); },
|
||||
}
|
||||
@assert(copy.storage.len == 3, "assign logical length");
|
||||
expect_bytes(copy, "wne");
|
||||
|
||||
var moved = take(©);
|
||||
defer { drop(&moved); }
|
||||
@assert(copy.storage.raw == null && copy.storage.len == 0 && copy.storage.cap == 0);
|
||||
expect_bytes(moved, "wne");
|
||||
|
||||
var old_raw = moved.storage.raw;
|
||||
var old_len = moved.storage.len;
|
||||
var old_cap = moved.storage.cap;
|
||||
expect_overflow(append_repeat(&moved, 1, ~(0 as usize)));
|
||||
@assert(moved.storage.raw == old_raw && moved.storage.len == old_len &&
|
||||
moved.storage.cap == old_cap,
|
||||
"overflow preserves byte buffer");
|
||||
expect_bytes(moved, "wne");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_append_and_resize();
|
||||
test_growth_and_self_append();
|
||||
test_ownership_and_failures();
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 借用的字节字符串视图;不拥有、不分配也不释放 ptr 指向的存储。
|
||||
// 非空视图要求 ptr 在整个借用期内可读。空视图统一表示为 null/0。
|
||||
|
||||
type .{ .Error, .make } = @import("error.spl");
|
||||
|
||||
#extern(vm) fn vm_bytes_eq(a: *any, alen: usize, b: *any, blen: usize): i32;
|
||||
|
||||
type ByteView = @self;
|
||||
|
||||
ptr: *u8,
|
||||
len: usize,
|
||||
|
||||
type ByteResult = enum {
|
||||
Ok: u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type ViewResult = enum {
|
||||
Ok: ByteView,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type FindResult = enum {
|
||||
Found: usize,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
fn empty(): ByteView {
|
||||
ret ByteView { .ptr = null, .len = 0 };
|
||||
}
|
||||
|
||||
// 输入切片的存储仍由调用方拥有。零长度输入被规范化为空视图。
|
||||
fn from_slice(bytes: []u8): ByteView {
|
||||
if bytes.len == 0 { ret empty(); }
|
||||
ret ByteView { .ptr = bytes.ptr, .len = bytes.len };
|
||||
}
|
||||
|
||||
// 以 NUL 结尾的 C 字符串转为视图;不复制、不拥有。null 规范化为空视图。
|
||||
fn from_cstr(text: *u8): ByteView {
|
||||
if text == null { ret empty(); }
|
||||
var len: usize = 0;
|
||||
while text[len] != 0 { len = len + 1; }
|
||||
ret from_slice(.{ .ptr = text, .len = len });
|
||||
}
|
||||
|
||||
fn as_slice(self: ByteView): []u8 {
|
||||
ret .{ .ptr = self.ptr, .len = self.len };
|
||||
}
|
||||
|
||||
fn size(self: ByteView): usize {
|
||||
ret self.len;
|
||||
}
|
||||
|
||||
fn is_empty(self: ByteView): bool {
|
||||
ret self.len == 0;
|
||||
}
|
||||
|
||||
fn byte_at(self: ByteView, index: usize): ByteResult {
|
||||
if index >= self.len { ret .Err(make(.OutOfRange)); }
|
||||
if self.ptr == null { ret .Err(make(.InvalidArgument)); }
|
||||
ret .Ok(self.ptr[index]);
|
||||
}
|
||||
|
||||
fn eq(a: ByteView, b: ByteView): bool {
|
||||
ret vm_bytes_eq(a.ptr as *any, a.len, b.ptr as *any, b.len) != 0;
|
||||
}
|
||||
|
||||
// 按无符号字节进行字典序比较,结果只承诺小于、等于或大于零。
|
||||
fn cmp(a: ByteView, b: ByteView): i32 {
|
||||
var count = a.len;
|
||||
if b.len < count { count = b.len; }
|
||||
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
var av = a.ptr[i];
|
||||
var bv = b.ptr[i];
|
||||
if av < bv { ret -1; }
|
||||
if av > bv { ret 1; }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if a.len < b.len { ret -1; }
|
||||
if a.len > b.len { ret 1; }
|
||||
ret 0;
|
||||
}
|
||||
|
||||
// 返回半开区间 [start, end)。失败不会产生部分视图。
|
||||
fn slice(self: ByteView, start: usize, end: usize): ViewResult {
|
||||
if start > end || end > self.len {
|
||||
ret .Err(make(.OutOfRange));
|
||||
}
|
||||
if start == end { ret .Ok(empty()); }
|
||||
if self.ptr == null { ret .Err(make(.InvalidArgument)); }
|
||||
|
||||
var address = self.ptr as usize;
|
||||
var limit: usize = ~(0 as usize);
|
||||
if start > limit - address { ret .Err(make(.Overflow)); }
|
||||
ret .Ok(ByteView { .ptr = (address + start) as *u8, .len = end - start });
|
||||
}
|
||||
|
||||
fn find(haystack: ByteView, needle: ByteView): FindResult {
|
||||
if needle.len == 0 { ret .Found(0); }
|
||||
if needle.len > haystack.len { ret .NotFound; }
|
||||
|
||||
var last = haystack.len - needle.len;
|
||||
var start: usize = 0;
|
||||
while start <= last {
|
||||
var i: usize = 0;
|
||||
while i < needle.len && haystack.ptr[start + i] == needle.ptr[i] {
|
||||
i = i + 1;
|
||||
}
|
||||
if i == needle.len { ret .Found(start); }
|
||||
start = start + 1;
|
||||
}
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
fn contains(haystack: ByteView, needle: ByteView): bool {
|
||||
match find(haystack, needle) {
|
||||
.Found(index) => { ret true; },
|
||||
.NotFound => { ret false; },
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with(self: ByteView, prefix: ByteView): bool {
|
||||
if prefix.len > self.len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < prefix.len {
|
||||
if self.ptr[i] != prefix.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn ends_with(self: ByteView, suffix: ByteView): bool {
|
||||
if suffix.len > self.len { ret false; }
|
||||
var start = self.len - suffix.len;
|
||||
var i: usize = 0;
|
||||
while i < suffix.len {
|
||||
if self.ptr[start + i] != suffix.ptr[i] { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn expect_byte(result: ByteResult, expected: u8) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(value == expected, "byte result"); },
|
||||
.Err(error) => { @assert(false, "unexpected byte error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_byte_error(result: ByteResult, out_of_range: bool) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected byte error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => { @assert(out_of_range, "unexpected byte range error"); },
|
||||
.InvalidArgument => { @assert(!out_of_range, "unexpected invalid view"); },
|
||||
_ => { @assert(false, "unexpected byte error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_slice(result: ViewResult, expected: ByteView) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(eq(value, expected), "slice result"); },
|
||||
.Err(error) => { @assert(false, "unexpected slice error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_slice_error(result: ViewResult) {
|
||||
match result {
|
||||
.Ok(value) => { @assert(false, "expected slice error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => {},
|
||||
_ => { @assert(false, "unexpected slice error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_found(result: FindResult, expected: usize) {
|
||||
match result {
|
||||
.Found(index) => { @assert(index == expected, "find position"); },
|
||||
.NotFound => { @assert(false, "expected match"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_not_found(result: FindResult) {
|
||||
match result {
|
||||
.Found(index) => { @assert(false, "unexpected match"); },
|
||||
.NotFound => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_construction_and_access() {
|
||||
var canonical = empty();
|
||||
@assert(canonical.ptr == null && canonical.len == 0, "canonical empty");
|
||||
|
||||
var data: [5]u8 = .[104, 101, 0, 108, 111];
|
||||
var text = from_slice(data[0..5]);
|
||||
@assert(size(text) == 5);
|
||||
@assert(!is_empty(text));
|
||||
expect_byte(byte_at(text, 0), 104);
|
||||
expect_byte(byte_at(text, 2), 0);
|
||||
expect_byte(byte_at(text, 4), 111);
|
||||
expect_byte_error(byte_at(text, 5), true);
|
||||
|
||||
var normalized = from_slice(data[3..3]);
|
||||
@assert(normalized.ptr == null && normalized.len == 0, "empty input normalized");
|
||||
expect_byte_error(byte_at(ByteView { .ptr = null, .len = 1 }, 0), false);
|
||||
|
||||
var bytes = as_slice(text);
|
||||
@assert(bytes.ptr == text.ptr && bytes.len == text.len, "slice conversion borrows");
|
||||
|
||||
var cstr_data: [4]u8 = .[104, 105, 0, 88];
|
||||
var cstr = from_cstr(&cstr_data[0]);
|
||||
@assert(cstr.len == 2 && cstr.ptr[0] == 104 && cstr.ptr[1] == 105,
|
||||
"from_cstr stops at NUL");
|
||||
@assert(from_cstr(&cstr_data[2]).len == 0, "from_cstr at NUL is empty");
|
||||
var none = from_cstr(null);
|
||||
@assert(none.ptr == null && none.len == 0, "from_cstr null normalized");
|
||||
}
|
||||
|
||||
fn test_compare_and_slice() {
|
||||
var hello = from_slice("hello");
|
||||
var same = from_slice("hello");
|
||||
var help = from_slice("help");
|
||||
var high_data: [1]u8 = .[255];
|
||||
var low_data: [1]u8 = .[127];
|
||||
|
||||
@assert(eq(hello, same));
|
||||
@assert(!eq(hello, help));
|
||||
@assert(eq(empty(), from_slice("")));
|
||||
@assert(cmp(hello, same) == 0);
|
||||
@assert(cmp(hello, help) < 0);
|
||||
@assert(cmp(help, hello) > 0);
|
||||
@assert(cmp(from_slice(low_data[0..1]), from_slice(high_data[0..1])) < 0,
|
||||
"comparison uses unsigned bytes");
|
||||
|
||||
expect_slice(slice(hello, 0, 5), hello);
|
||||
expect_slice(slice(hello, 1, 4), from_slice("ell"));
|
||||
match slice(hello, 2, 2) {
|
||||
.Ok(value) => {
|
||||
@assert(value.ptr == null && value.len == 0, "empty subslice normalized");
|
||||
},
|
||||
.Err(error) => { @assert(false, "empty subslice"); },
|
||||
}
|
||||
expect_slice_error(slice(hello, 4, 3));
|
||||
expect_slice_error(slice(hello, 0, 6));
|
||||
}
|
||||
|
||||
fn test_search() {
|
||||
var text = from_slice("ababa");
|
||||
expect_found(find(text, from_slice("aba")), 0);
|
||||
expect_found(find(text, from_slice("bab")), 1);
|
||||
expect_found(find(text, from_slice("ba")), 1);
|
||||
expect_found(find(text, from_slice("")), 0);
|
||||
expect_not_found(find(text, from_slice("abb")));
|
||||
expect_not_found(find(text, from_slice("ababaa")));
|
||||
|
||||
@assert(contains(text, from_slice("aba")));
|
||||
@assert(!contains(text, from_slice("xyz")));
|
||||
@assert(starts_with(text, from_slice("ab")));
|
||||
@assert(starts_with(text, empty()));
|
||||
@assert(!starts_with(text, from_slice("ba")));
|
||||
@assert(ends_with(text, from_slice("ba")));
|
||||
@assert(ends_with(text, empty()));
|
||||
@assert(!ends_with(text, from_slice("ab")));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_construction_and_access();
|
||||
test_compare_and_slice();
|
||||
test_search();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 公共错误值不持有资源,可按值复制;不分配错误消息字符串。
|
||||
// position 是调用方输入中的从零开始的位置;仅在 has_position 时有效。
|
||||
// 未定位错误的 position 规范为 0,不能用 position == 0 判断是否有位置。
|
||||
// 数值解析位置按输入字节计数。可用内存耗尽仍由现有 VM 终止处理。
|
||||
|
||||
type Error = @self;
|
||||
|
||||
type Kind = enum {
|
||||
InvalidArgument,
|
||||
OutOfRange,
|
||||
Overflow,
|
||||
DivisionByZero,
|
||||
InvalidDigit,
|
||||
InvalidFormat,
|
||||
UnexpectedEnd,
|
||||
InvalidAlignment,
|
||||
WidthMismatch,
|
||||
Io,
|
||||
}
|
||||
|
||||
kind: Kind,
|
||||
has_position: bool,
|
||||
position: usize,
|
||||
|
||||
// 无成功载荷的结果。带成功值的模块定义自己的 Ok: T / Err: Error 枚举。
|
||||
type Status = enum {
|
||||
Ok,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn make(kind: Kind): Error {
|
||||
ret Error { .kind = kind, .has_position = false, .position = 0 };
|
||||
}
|
||||
|
||||
fn at(kind: Kind, position: usize): Error {
|
||||
ret Error { .kind = kind, .has_position = true, .position = position };
|
||||
}
|
||||
|
||||
fn test_status(result: Status, expected_error: bool) {
|
||||
match result {
|
||||
.Ok => {
|
||||
@assert(!expected_error, "success variant");
|
||||
},
|
||||
.Err(error) => {
|
||||
@assert(expected_error, "error variant");
|
||||
@assert(!error.has_position, "unlocated error");
|
||||
@assert(error.position == 0, "canonical absent position");
|
||||
match error.kind {
|
||||
.Overflow => {},
|
||||
_ => { @assert(false, "error kind preserved in result"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
var success: Status = .Ok;
|
||||
test_status(success, false);
|
||||
var failure: Status = .Err(make(.Overflow));
|
||||
test_status(failure, true);
|
||||
|
||||
var first = at(.InvalidDigit, 0);
|
||||
@assert(first.has_position, "byte zero is a valid position");
|
||||
@assert(first.position == 0);
|
||||
match first.kind {
|
||||
.InvalidDigit => {},
|
||||
_ => { @assert(false, "positioned error kind"); },
|
||||
}
|
||||
|
||||
var original = at(.UnexpectedEnd, 17);
|
||||
var copy = original;
|
||||
copy.position = 3;
|
||||
@assert(original.position == 17, "error copy has independent fields");
|
||||
@assert(copy.position == 3);
|
||||
@assert(copy.has_position);
|
||||
|
||||
var max_position: usize = ~0 as usize;
|
||||
var last = at(.OutOfRange, max_position);
|
||||
@assert(last.has_position, "no reserved position sentinel");
|
||||
@assert(last.position == max_position);
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
|
||||
// 整数转换统一复用 MPA。先在临时缓冲区完成转换,再一次性追加,失败不产生部分输出。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .Value, .int, .uint, .char, .str, .addr } = @import("value.spl");
|
||||
type .{ .BigInt, .Bits } = @import("mp.spl");
|
||||
|
||||
// 注意:radix 边界直接用字面量。stage1 刻意不做常量求值/折叠,
|
||||
// 顶层 const 不应用于运行时表达式。
|
||||
fn valid_radix(radix: u8): bool {
|
||||
ret radix >= 2 && radix <= 36;
|
||||
}
|
||||
|
||||
fn append_big_int(out: *ByteBuffer, value: BigInt,
|
||||
radix: u8, uppercase: bool): Status {
|
||||
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
|
||||
var text = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&text); }
|
||||
match BigInt.format(&text, value, radix, uppercase) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret ByteBuffer.append(out, ByteBuffer.as_slice(text));
|
||||
}
|
||||
|
||||
fn append_bits(out: *ByteBuffer, value: Bits, signed: bool,
|
||||
radix: u8, uppercase: bool): Status {
|
||||
var integer = BigInt.zero();
|
||||
defer { BigInt.drop(&integer); }
|
||||
match Bits.to_big(&integer, value, signed) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret append_big_int(out, integer, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
|
||||
var integer = BigInt.zero();
|
||||
defer { BigInt.drop(&integer); }
|
||||
match BigInt.from_u64(&integer, value) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret append_big_int(out, integer, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_i64(out: *ByteBuffer, value: i64, radix: u8, uppercase: bool): Status {
|
||||
var integer = BigInt.zero();
|
||||
defer { BigInt.drop(&integer); }
|
||||
match BigInt.from_i64(&integer, value) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret append_big_int(out, integer, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_usize(out: *ByteBuffer, value: usize, radix: u8, uppercase: bool): Status {
|
||||
ret append_u64(out, value as u64, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_isize(out: *ByteBuffer, value: isize, radix: u8, uppercase: bool): Status {
|
||||
ret append_i64(out, value as i64, radix, uppercase);
|
||||
}
|
||||
|
||||
fn append_bool(out: *ByteBuffer, value: bool): Status {
|
||||
if value { ret ByteBuffer.append(out, "true"); }
|
||||
ret ByteBuffer.append(out, "false");
|
||||
}
|
||||
|
||||
// ---- 显式装箱值格式化 ----
|
||||
|
||||
// Int/Uint 落到整数;signed 仅对 Int 的十进制生效,其余按无符号位模式输出。
|
||||
fn append_value_int(out: *ByteBuffer, value: Value, radix: u8, uppercase: bool,
|
||||
signed: bool): Status {
|
||||
match value {
|
||||
.Int(v) => {
|
||||
if signed && radix == 10 { ret append_i64(out, v, radix, uppercase); }
|
||||
ret append_u64(out, v as u64, radix, uppercase);
|
||||
},
|
||||
.Uint(v) => { ret append_u64(out, v, radix, uppercase); },
|
||||
_ => { ret .Err(make(.InvalidFormat)); },
|
||||
}
|
||||
}
|
||||
|
||||
fn append_value_char(out: *ByteBuffer, value: Value): Status {
|
||||
match value {
|
||||
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
|
||||
_ => { ret .Err(make(.InvalidFormat)); },
|
||||
}
|
||||
}
|
||||
|
||||
fn append_value_str(out: *ByteBuffer, value: Value): Status {
|
||||
match value {
|
||||
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
|
||||
_ => { ret .Err(make(.InvalidFormat)); },
|
||||
}
|
||||
}
|
||||
|
||||
fn append_value_ptr(out: *ByteBuffer, value: Value): Status {
|
||||
match value {
|
||||
.Ptr(p) => {
|
||||
match ByteBuffer.append(out, "0x") {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret append_u64(out, p as usize, 16, false);
|
||||
},
|
||||
_ => { ret .Err(make(.InvalidFormat)); },
|
||||
}
|
||||
}
|
||||
|
||||
// {} 自动格式化:按 Value 变体选择默认表示。
|
||||
fn append_value_auto(out: *ByteBuffer, value: Value): Status {
|
||||
match value {
|
||||
.Int(v) => { ret append_i64(out, v, 10, false); },
|
||||
.Uint(v) => { ret append_u64(out, v, 10, false); },
|
||||
.Bool(v) => { ret append_bool(out, v); },
|
||||
.Char(c) => { ret ByteBuffer.append_byte(out, c); },
|
||||
.Str(view) => { ret ByteBuffer.append(out, ByteView.as_slice(view)); },
|
||||
.Ptr(p) => {
|
||||
match ByteBuffer.append(out, "0x") {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret append_u64(out, p as usize, 16, false);
|
||||
},
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// {} 风格:{} 自动;{:d} {:i} {:u} {:x} {:X} {:o} {:b} {:c} {:s} {:p} 指定。
|
||||
// {{ 与 }} 输出字面量括号。参数按序从 args 消费;数量不足、类型不符、未知转换
|
||||
// 或括号不配返回 Err(.InvalidFormat)。失败不产生部分输出:先在临时缓冲完成。
|
||||
fn format(out: *ByteBuffer, fmt: []u8, args: []Value): Status {
|
||||
var temp = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&temp); }
|
||||
|
||||
var argi: usize = 0;
|
||||
var i: usize = 0;
|
||||
while i < fmt.len {
|
||||
var c = fmt.ptr[i];
|
||||
|
||||
if c == '{' {
|
||||
if i + 1 < fmt.len && fmt.ptr[i + 1] == '{' {
|
||||
match ByteBuffer.append_byte(&temp, '{') {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
i = i + 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
var j = i + 1;
|
||||
if j < fmt.len && fmt.ptr[j] == ':' { j = j + 1; }
|
||||
var has_spec = false;
|
||||
var spec: u8 = 0;
|
||||
if j < fmt.len && fmt.ptr[j] != '}' {
|
||||
has_spec = true;
|
||||
spec = fmt.ptr[j];
|
||||
j = j + 1;
|
||||
}
|
||||
if j >= fmt.len || fmt.ptr[j] != '}' { ret .Err(make(.InvalidFormat)); }
|
||||
i = j + 1;
|
||||
|
||||
if argi >= args.len { ret .Err(make(.InvalidFormat)); }
|
||||
var value = args.ptr[argi];
|
||||
argi = argi + 1;
|
||||
|
||||
var step: Status = .Ok;
|
||||
if !has_spec {
|
||||
step = append_value_auto(&temp, value);
|
||||
} else if spec == 'd' || spec == 'i' {
|
||||
step = append_value_int(&temp, value, 10, false, true);
|
||||
} else if spec == 'u' {
|
||||
step = append_value_int(&temp, value, 10, false, false);
|
||||
} else if spec == 'x' {
|
||||
step = append_value_int(&temp, value, 16, false, false);
|
||||
} else if spec == 'X' {
|
||||
step = append_value_int(&temp, value, 16, true, false);
|
||||
} else if spec == 'o' {
|
||||
step = append_value_int(&temp, value, 8, false, false);
|
||||
} else if spec == 'b' {
|
||||
step = append_value_int(&temp, value, 2, false, false);
|
||||
} else if spec == 'c' {
|
||||
step = append_value_char(&temp, value);
|
||||
} else if spec == 's' {
|
||||
step = append_value_str(&temp, value);
|
||||
} else if spec == 'p' {
|
||||
step = append_value_ptr(&temp, value);
|
||||
} else {
|
||||
ret .Err(make(.InvalidFormat));
|
||||
}
|
||||
match step {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '}' {
|
||||
if i + 1 < fmt.len && fmt.ptr[i + 1] == '}' {
|
||||
match ByteBuffer.append_byte(&temp, '}') {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
i = i + 2;
|
||||
continue;
|
||||
}
|
||||
ret .Err(make(.InvalidFormat));
|
||||
}
|
||||
|
||||
match ByteBuffer.append_byte(&temp, c) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
ret ByteBuffer.append(out, ByteBuffer.as_slice(temp));
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected fmt error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_invalid(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected fmt error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidArgument => {},
|
||||
_ => { @assert(false, "unexpected fmt error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_text(buffer: *ByteBuffer, expected: []u8) {
|
||||
var actual = ByteBuffer.as_slice(buffer.*);
|
||||
@assert(actual.len == expected.len, "formatted length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_unsigned() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(append_u64(&b, 0, 10, false));
|
||||
expect_text(&b, "0");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 42, 10, false));
|
||||
expect_text(&b, "42");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 1234567890, 10, false));
|
||||
expect_text(&b, "1234567890");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 18446744073709551615, 10, false));
|
||||
expect_text(&b, "18446744073709551615");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 255, 16, false));
|
||||
expect_text(&b, "ff");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 255, 16, true));
|
||||
expect_text(&b, "FF");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 4096, 16, false));
|
||||
expect_text(&b, "1000");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 170, 2, false));
|
||||
expect_text(&b, "10101010");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 35, 36, false));
|
||||
expect_text(&b, "z");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 18446744073709551615, 2, false));
|
||||
expect_text(&b, "1111111111111111111111111111111111111111111111111111111111111111");
|
||||
ByteBuffer.clear(&b);
|
||||
}
|
||||
|
||||
fn test_signed() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(append_i64(&b, 0, 10, false));
|
||||
expect_text(&b, "0");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_i64(&b, -123, 10, false));
|
||||
expect_text(&b, "-123");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_i64(&b, 9223372036854775807, 10, false));
|
||||
expect_text(&b, "9223372036854775807");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_i64(&b, -9223372036854775808, 10, false));
|
||||
expect_text(&b, "-9223372036854775808");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_isize(&b, -16, 16, false));
|
||||
expect_text(&b, "-10");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_u64(&b, 48879, 16, true));
|
||||
expect_text(&b, "BEEF");
|
||||
ByteBuffer.clear(&b);
|
||||
}
|
||||
|
||||
fn test_usize_bool_and_errors() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(append_usize(&b, 1000 as usize, 10, false));
|
||||
expect_text(&b, "1000");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_bool(&b, true));
|
||||
expect_text(&b, "true");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(append_bool(&b, false));
|
||||
expect_text(&b, "false");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_invalid(append_u64(&b, 1, 0, false));
|
||||
expect_invalid(append_u64(&b, 1, 1, false));
|
||||
expect_invalid(append_u64(&b, 1, 37, false));
|
||||
expect_invalid(append_i64(&b, -1, 0, false));
|
||||
@assert(ByteBuffer.size(b) == 0, "invalid radix writes nothing");
|
||||
|
||||
expect_ok(ByteBuffer.append(&b, "prefix"));
|
||||
expect_invalid(append_i64(&b, -123, 1, false));
|
||||
expect_text(&b, "prefix");
|
||||
}
|
||||
|
||||
fn test_mpa_integer_formatting() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
var huge = BigInt.zero();
|
||||
defer { BigInt.drop(&huge); }
|
||||
expect_ok(BigInt.parse(&huge, "1234567890123456789012345678901234567890", 10));
|
||||
expect_ok(append_big_int(&b, huge, 10, false));
|
||||
expect_text(&b, "1234567890123456789012345678901234567890");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
expect_ok(BigInt.parse(&huge, "-ffffffffffffffffffffffffffffffff", 16));
|
||||
expect_ok(append_big_int(&b, huge, 16, true));
|
||||
expect_text(&b, "-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
var raw = BigInt.zero();
|
||||
defer { BigInt.drop(&raw); }
|
||||
expect_ok(BigInt.from_u64(&raw, 255));
|
||||
var bits: Bits = undefined;
|
||||
match Bits.new(8) {
|
||||
.Ok(value) => { bits = value; },
|
||||
.Err(error) => { @assert(false, "Bits construction"); },
|
||||
}
|
||||
defer { Bits.drop(&bits); }
|
||||
expect_ok(Bits.from_big_checked(&bits, raw, 8, false));
|
||||
|
||||
expect_ok(append_bits(&b, bits, false, 10, false));
|
||||
expect_text(&b, "255");
|
||||
ByteBuffer.clear(&b);
|
||||
expect_ok(append_bits(&b, bits, true, 10, false));
|
||||
expect_text(&b, "-1");
|
||||
}
|
||||
|
||||
fn test_composition_and_growth() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(ByteBuffer.append(&b, "x="));
|
||||
expect_ok(append_i64(&b, -42, 10, false));
|
||||
expect_ok(ByteBuffer.append(&b, " hex="));
|
||||
expect_ok(append_u64(&b, 255, 16, false));
|
||||
expect_ok(ByteBuffer.append(&b, " ok="));
|
||||
expect_ok(append_bool(&b, true));
|
||||
expect_text(&b, "x=-42 hex=ff ok=true");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
// 反复写入触发 ByteBuffer 扩容,校验累积内容。
|
||||
var i: i32 = 0;
|
||||
while i < 200 {
|
||||
expect_ok(append_i64(&b, i as i64, 10, false));
|
||||
expect_ok(ByteBuffer.append_byte(&b, ',')); // ','
|
||||
i = i + 1;
|
||||
}
|
||||
expect_ok(append_i64(&b, 200, 10, false));
|
||||
@assert(ByteBuffer.size(b) > 0, "growth content");
|
||||
var s = ByteBuffer.as_slice(b);
|
||||
@assert(s.ptr[0] == '0', "starts with 0");
|
||||
@assert(s.ptr[1] == ',', "0 then comma");
|
||||
}
|
||||
|
||||
fn expect_format(fmt: []u8, args: []Value, expected: []u8) {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
expect_ok(format(&b, fmt, args));
|
||||
expect_text(&b, expected);
|
||||
}
|
||||
|
||||
fn expect_invalid_format(fmt: []u8, args: []Value) {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
match format(&b, fmt, args) {
|
||||
.Ok => { @assert(false, "expected invalid format"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.InvalidFormat => {},
|
||||
_ => { @assert(false, "expected InvalidFormat kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_format_specs() {
|
||||
var one: [1]Value = .[int(-5)];
|
||||
expect_format("{}", one[0..1], "-5");
|
||||
expect_format("{d}", one[0..1], "-5");
|
||||
expect_format("{i}", one[0..1], "-5");
|
||||
|
||||
var u: [1]Value = .[uint(7)];
|
||||
expect_format("{}", u[0..1], "7");
|
||||
expect_format("{u}", u[0..1], "7");
|
||||
expect_format("{x}", u[0..1], "7");
|
||||
expect_format("{o}", u[0..1], "7");
|
||||
expect_format("{b}", u[0..1], "111");
|
||||
|
||||
var hex: [1]Value = .[uint(255)];
|
||||
expect_format("{x}", hex[0..1], "ff");
|
||||
expect_format("{X}", hex[0..1], "FF");
|
||||
|
||||
var oct: [1]Value = .[uint(8)];
|
||||
expect_format("{o}", oct[0..1], "10");
|
||||
|
||||
var bits: [1]Value = .[uint(5)];
|
||||
expect_format("{b}", bits[0..1], "101");
|
||||
|
||||
var ch: [1]Value = .[char('A')];
|
||||
expect_format("{}", ch[0..1], "A");
|
||||
expect_format("{c}", ch[0..1], "A");
|
||||
|
||||
var text: [1]Value = .[str("hi")];
|
||||
expect_format("{}", text[0..1], "hi");
|
||||
expect_format("{s}", text[0..1], "hi");
|
||||
|
||||
var nul: [1]Value = .[addr(null)];
|
||||
expect_format("{p}", nul[0..1], "0x0");
|
||||
|
||||
var none: [1]Value = .[int(0)];
|
||||
expect_format("100%", none[0..0], "100%");
|
||||
expect_format("{{}}", none[0..0], "{}");
|
||||
expect_format("plain", none[0..0], "plain");
|
||||
}
|
||||
|
||||
fn test_format_mixed_and_failure() {
|
||||
var args: [3]Value = .[int(42), str("x"), char('!')];
|
||||
expect_format("n={} s={} c={}", args[0..3], "n=42 s=x c=!");
|
||||
expect_format("n={} s={s} c={c}", args[0..3], "n=42 s=x c=!");
|
||||
|
||||
// 失败不改动输出,且不产生部分结果:前两个转换成功、第三个参数不足。
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
expect_ok(ByteBuffer.append(&b, "prefix"));
|
||||
var two: [2]Value = .[int(1), int(2)];
|
||||
expect_invalid_format("a={} b={} c={}", two[0..2]);
|
||||
expect_text(&b, "prefix");
|
||||
|
||||
var only_int: [1]Value = .[int(1)];
|
||||
expect_invalid_format("{} {}", only_int[0..1]);
|
||||
expect_invalid_format("{}", only_int[0..0]);
|
||||
expect_invalid_format("trailing {", only_int[0..1]);
|
||||
expect_invalid_format("dangling }", only_int[0..1]);
|
||||
expect_invalid_format("{q}", only_int[0..1]);
|
||||
expect_invalid_format("{s}", only_int[0..1]);
|
||||
expect_invalid_format("{c}", only_int[0..1]);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_unsigned();
|
||||
test_signed();
|
||||
test_usize_bool_and_errors();
|
||||
test_mpa_integer_formatting();
|
||||
test_composition_and_growth();
|
||||
test_format_specs();
|
||||
test_format_mixed_and_failure();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 哈希工具:FNV-1a 字节哈希 + 整型混合;并给出可直接交给 map 的回调
|
||||
// (map 的 hash/eq 回调接收指向键字节的指针,这里按 usize / ByteView 解释)。
|
||||
// 仅提供确定性哈希与等值,不承诺密码学性质。
|
||||
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
|
||||
// FNV-1a 64 位,逐字节;空输入返回 offset basis。
|
||||
fn hash_bytes(data: *u8, len: usize): usize {
|
||||
var h: usize = 14695981039346656037; // FNV-1a 64 位 offset basis
|
||||
var prime: usize = 1099511628211; // FNV 质数
|
||||
var i: usize = 0;
|
||||
while i < len {
|
||||
h = (h ^ (data[i] as usize)) * prime;
|
||||
i = i + 1;
|
||||
}
|
||||
ret h;
|
||||
}
|
||||
|
||||
fn hash_byte_view(view: ByteView): usize {
|
||||
ret hash_bytes(view.ptr, view.len);
|
||||
}
|
||||
|
||||
// 整型混合(splitmix 风格的移位-乘法),保证非零与良好扩散。
|
||||
fn hash_usize_value(value: usize): usize {
|
||||
var prime: usize = 1099511628211; // splitmix 风格的混合乘子
|
||||
var h = value;
|
||||
h = (h ^ (h >> 30)) * prime;
|
||||
h = h ^ (h >> 27);
|
||||
h = h * prime;
|
||||
h = h ^ (h >> 31);
|
||||
if h == 0 { ret prime; } // 0 映射到非零
|
||||
ret h;
|
||||
}
|
||||
|
||||
// ---- map 回调 ----
|
||||
|
||||
// 键按 usize 解释。
|
||||
fn hash_usize(key: *u8): usize {
|
||||
ret hash_usize_value((key as *usize).*);
|
||||
}
|
||||
|
||||
fn eq_usize(a: *u8, b: *u8): bool {
|
||||
ret (a as *usize).* == (b as *usize).*;
|
||||
}
|
||||
|
||||
// 键按 ByteView 解释,哈希其指向的字节。
|
||||
fn hash_view(key: *u8): usize {
|
||||
var view = (key as *ByteView).*;
|
||||
ret hash_byte_view(view);
|
||||
}
|
||||
|
||||
fn eq_view(a: *u8, b: *u8): bool {
|
||||
ret ByteView.eq((a as *ByteView).*, (b as *ByteView).*);
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn expect_hash(view: []u8, expected: usize) {
|
||||
var got = hash_bytes(view.ptr, view.len);
|
||||
@assert(got == expected, "hash value");
|
||||
}
|
||||
|
||||
fn test_fnv_vectors() {
|
||||
expect_hash("", 14695981039346656037);
|
||||
expect_hash("a", 12638187200555641996);
|
||||
expect_hash("foobar", 9625390261332436968);
|
||||
expect_hash("hello", 11831194018420276491);
|
||||
|
||||
// 同一输入稳定;不同输入不同。
|
||||
var s1: []u8 = "abc";
|
||||
var s2: []u8 = "abd";
|
||||
@assert(hash_bytes(s1.ptr, 3) == hash_bytes(s1.ptr, 3), "deterministic");
|
||||
@assert(hash_bytes(s1.ptr, 3) != hash_bytes(s2.ptr, 3), "distinct");
|
||||
|
||||
// ByteView 与 bytes 一致。
|
||||
var v = ByteView.from_slice("foobar");
|
||||
@assert(hash_byte_view(v) == hash_bytes(v.ptr, v.len), "view == bytes");
|
||||
|
||||
// 整型混合:0 不保持 0,且稳定/区分。
|
||||
@assert(hash_usize_value(0) != 0, "zero mixed");
|
||||
@assert(hash_usize_value(7) == hash_usize_value(7), "int stable");
|
||||
@assert(hash_usize_value(7) != hash_usize_value(8), "int distinct");
|
||||
}
|
||||
|
||||
// map 回调(hash_usize/eq_usize/hash_view/eq_view)由 map.spl 在自己的测试里验证。
|
||||
|
||||
fn main() {
|
||||
test_fnv_vectors();
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// I/O 便利层(构建在 sys/file 之上):
|
||||
// - C 字符串路径/模式的 open 包装
|
||||
// - 精确读取、完整写入和普通文件整读
|
||||
// - stdout/stderr 打印
|
||||
// 说明:read_all 会按文件大小一次性读入 ByteBuffer;不处理增长中的文件。
|
||||
// 短读、短写后的零进展及关闭失败返回 Err(.Io);文件写入不承诺回滚。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type File = @import("../sys/file.spl");
|
||||
type Mem = @import("../sys/mem.spl");
|
||||
|
||||
// 把 view 复制到以 NUL 结尾的临时缓冲,返回其 data(归属 tmp,调用后勿再用)。
|
||||
fn cstr(tmp: *ByteBuffer, view: ByteView): *u8 {
|
||||
ByteBuffer.clear(tmp);
|
||||
if view.len != 0 {
|
||||
match ByteBuffer.append(tmp, ByteView.as_slice(view)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
}
|
||||
match ByteBuffer.append_byte(tmp, '\0') {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret tmp.storage.data;
|
||||
}
|
||||
|
||||
fn open_cstr(path: ByteView, mode: ByteView): File {
|
||||
var pbuf = ByteBuffer.new();
|
||||
var mbuf = ByteBuffer.new();
|
||||
var p = cstr(&pbuf, path);
|
||||
var m = cstr(&mbuf, mode);
|
||||
var f = File { .handle = null };
|
||||
if p != null && m != null { f = File.open(p, m); }
|
||||
ByteBuffer.drop(&pbuf);
|
||||
ByteBuffer.drop(&mbuf);
|
||||
ret f;
|
||||
}
|
||||
|
||||
// 反复写入直到完成。发生错误时文件可能已经包含前缀,不承诺回滚。
|
||||
fn write_all(f: *File, bytes: []u8): Status {
|
||||
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
|
||||
if bytes.len != 0 && bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
|
||||
var total: usize = 0;
|
||||
while total < bytes.len {
|
||||
var written = File.write(f, bytes.ptr + total, (bytes.len - total) as u64);
|
||||
if written == 0 || written > (bytes.len - total) as u64 {
|
||||
ret .Err(make(.Io));
|
||||
}
|
||||
total = total + written as usize;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn print(bytes: []u8): Status {
|
||||
var out = File.stdout();
|
||||
ret write_all(&out, bytes);
|
||||
}
|
||||
|
||||
fn println(bytes: []u8): Status {
|
||||
match print(bytes) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret print("\n");
|
||||
}
|
||||
|
||||
fn eprint(bytes: []u8): Status {
|
||||
var err = File.stderr();
|
||||
ret write_all(&err, bytes);
|
||||
}
|
||||
|
||||
// 仅供已经隔离的临时存储使用;失败时可能写入目标前缀。
|
||||
fn read_fully(f: *File, data: *u8, len: usize): Status {
|
||||
var total: usize = 0;
|
||||
while total < len {
|
||||
var got = File.read(f, data + total, len - total);
|
||||
if got == 0 || got > len - total { ret .Err(make(.Io)); }
|
||||
total = total + got;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 精确读取 bytes.len 字节。短读返回 Err(.Io),失败保持 bytes 原内容不变。
|
||||
fn read_exact(f: *File, bytes: []u8): Status {
|
||||
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
|
||||
if bytes.len == 0 { ret .Ok; }
|
||||
if bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
|
||||
|
||||
var temporary = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&temporary); }
|
||||
match ByteBuffer.reserve(&temporary, bytes.len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match read_fully(f, temporary.storage.data, bytes.len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
Mem.copy(bytes.ptr as *any, temporary.storage.data as *any, bytes.len);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 读取整个普通文件到 out。打开、短读或关闭失败时保持 out 不变。
|
||||
fn read_all(path: ByteView, out: *ByteBuffer): Status {
|
||||
if out == null { ret .Err(make(.InvalidArgument)); }
|
||||
var f = open_cstr(path, ByteView.from_slice("rb"));
|
||||
if !File.valid(&f) { ret .Err(make(.Io)); }
|
||||
|
||||
var sz = File.size(&f);
|
||||
var fresh = ByteBuffer.new();
|
||||
if sz != 0 {
|
||||
match ByteBuffer.reserve(&fresh, sz) {
|
||||
.Ok => {},
|
||||
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match read_fully(&f, fresh.storage.data, sz) {
|
||||
.Ok => {},
|
||||
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
fresh.storage.len = sz;
|
||||
}
|
||||
if File.close(&f) != 0 { ByteBuffer.drop(&fresh); ret .Err(make(.Io)); }
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn view(text: []u8): ByteView {
|
||||
ret ByteView.from_slice(text);
|
||||
}
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected io error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_io(result: Status) {
|
||||
match result {
|
||||
.Ok => { @assert(false, "expected io error"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.Io => {},
|
||||
_ => { @assert(false, "expected Io kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_roundtrip() {
|
||||
var path = view("build/_io_roundtrip.tmp");
|
||||
|
||||
var f = open_cstr(path, view("wb"));
|
||||
@assert(File.valid(&f), "open wb");
|
||||
expect_ok(write_all(&f, "hello\nworld"));
|
||||
@assert(File.close(&f) == 0, "close writer");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
match read_all(path, &out) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "read_all"); },
|
||||
}
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
@assert(got.len == 11, "roundtrip length");
|
||||
@assert(got.ptr[0] == 'h' && got.ptr[5] == '\n' && got.ptr[10] == 'd', "roundtrip bytes");
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn test_append_and_missing() {
|
||||
var path = view("build/_io_append.tmp");
|
||||
|
||||
var f = open_cstr(path, view("wb"));
|
||||
@assert(File.valid(&f), "open wb");
|
||||
expect_ok(write_all(&f, "ab"));
|
||||
@assert(File.close(&f) == 0, "close initial writer");
|
||||
|
||||
f = open_cstr(path, view("ab"));
|
||||
@assert(File.valid(&f), "open ab");
|
||||
expect_ok(write_all(&f, "cd"));
|
||||
@assert(File.close(&f) == 0, "close append writer");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
match read_all(path, &out) {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "read append"); },
|
||||
}
|
||||
var got = ByteBuffer.as_slice(out);
|
||||
@assert(got.len == 4 && got.ptr[2] == 'c' && got.ptr[3] == 'd', "append content");
|
||||
|
||||
// 不存在的文件
|
||||
match read_all(view("build/_io_no_such_file.tmp"), &out) {
|
||||
.Ok => { @assert(false, "missing should fail"); },
|
||||
.Err(error) => {
|
||||
match error.kind { .Io => {}, _ => { @assert(false, "error kind"); } }
|
||||
},
|
||||
}
|
||||
got = ByteBuffer.as_slice(out);
|
||||
@assert(got.len == 4 && got.ptr[0] == 'a' && got.ptr[3] == 'd',
|
||||
"failed read_all preserves output");
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn test_read_exact_and_errors() {
|
||||
var path = view("build/_io_exact.tmp");
|
||||
var f = open_cstr(path, view("wb"));
|
||||
@assert(File.valid(&f));
|
||||
expect_ok(write_all(&f, "xyz"));
|
||||
@assert(File.close(&f) == 0);
|
||||
|
||||
f = open_cstr(path, view("rb"));
|
||||
@assert(File.valid(&f));
|
||||
var too_long: [4]u8 = .[9, 8, 7, 6];
|
||||
expect_io(read_exact(&f, too_long[0..4]));
|
||||
@assert(too_long[0] == 9 && too_long[1] == 8 &&
|
||||
too_long[2] == 7 && too_long[3] == 6,
|
||||
"short read preserves destination");
|
||||
@assert(File.close(&f) == 0);
|
||||
|
||||
f = open_cstr(path, view("rb"));
|
||||
@assert(File.valid(&f));
|
||||
var exact: [3]u8 = .[0, 0, 0];
|
||||
expect_ok(read_exact(&f, exact[0..3]));
|
||||
@assert(exact[0] == 'x' && exact[1] == 'y' && exact[2] == 'z');
|
||||
@assert(File.close(&f) == 0);
|
||||
|
||||
var invalid = File { .handle = null };
|
||||
expect_io(write_all(&invalid, "x"));
|
||||
expect_io(read_exact(&invalid, exact[0..3]));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_roundtrip();
|
||||
test_append_and_missing();
|
||||
test_read_exact_and_errors();
|
||||
expect_ok(println("io: ok"));
|
||||
}
|
||||
Loaded 100 of 183 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user