cgen: fix hash stmt code generation (fix #25184) (#25207)
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run

This commit is contained in:
kbkpbot 2025-09-01 01:39:02 +08:00 committed by GitHub
parent d31aaecc42
commit 487feb9b0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 282 additions and 99 deletions

View file

@ -117,6 +117,8 @@ pub type Stmt = AsmStmt
| StructDecl
| TypeDecl
pub type HashStmtNode = IfExpr | HashStmt
pub struct EmptyScopeObject {
pub mut:
name string

View file

@ -274,6 +274,11 @@ mut:
export_funcs []string // for .dll export function names
//
type_default_impl_level int
preinclude_nodes []&ast.HashStmtNode // allows hash stmts to go before `includes`
include_nodes []&ast.HashStmtNode // all hash stmts to go `includes`
definition_nodes []&ast.HashStmtNode // allows hash stmts to go `definitions`
postinclude_nodes []&ast.HashStmtNode // allows hash stmts to go after all the rest of the code generation
curr_comptime_node &ast.Expr = unsafe { nil } // current `$if` expr
}
@[heap]
@ -920,6 +925,11 @@ pub fn (mut g Gen) gen_file() {
g.inside_ternary = 0
}
g.stmts(g.file.stmts)
// after all other stmts executed, we got info about hash stmts in top,
// write them to corresponding sections
g.gen_hash_stmts_in_top()
// Transfer embedded files
for path in g.file.embedded_files {
if path !in g.embedded_files {
@ -5740,113 +5750,178 @@ fn (mut g Gen) hash_stmt_guarded_include(node ast.HashStmt) string {
}
fn (mut g Gen) hash_stmt(node ast.HashStmt) {
line_nr := node.pos.line_nr + 1
mut ct_condition := ''
// we only record the hash stmt's node here, send it to corresponding `_nodes`, let `gen_hash_stmts_in_top()` gen the code.
the_node := if g.comptime.inside_comptime_if && !isnil(g.curr_comptime_node)
&& g.curr_comptime_node is ast.IfExpr {
&ast.HashStmtNode(g.curr_comptime_node as ast.IfExpr)
} else {
&ast.HashStmtNode(&node)
}
if node.ct_conds.len > 0 {
match node.kind {
'include', 'insert', 'define' {
if node.main.contains('.m') {
// Objective C code import, include it after V types, so that e.g. `string` is
// available there
if the_node !in g.definition_nodes {
g.definition_nodes << the_node
}
} else {
if the_node !in g.include_nodes {
g.include_nodes << the_node
}
}
}
'preinclude' {
if node.main.contains('.m') {
// Objective C code import, include it after V types, so that e.g. `string` is
// available there
if the_node !in g.definition_nodes {
g.definition_nodes << the_node
}
} else {
if the_node !in g.preinclude_nodes {
g.preinclude_nodes << the_node
}
}
}
'postinclude' {
if the_node !in g.postinclude_nodes {
g.postinclude_nodes << the_node
}
}
else {}
}
}
fn (mut g Gen) gen_hash_stmts(mut sb strings.Builder, node &ast.HashStmtNode, section string) {
match node {
ast.IfExpr {
mut comptime_branch_context_str := g.gen_branch_context_string()
mut is_true := ast.ComptTimeCondResult{}
mut sb := strings.new_builder(256)
for idx, ct_expr in node.ct_conds {
idx_str := comptime_branch_context_str + '|${g.file.path}|${ct_expr.pos()}|'
for i, branch in node.branches {
idx_str := if branch.cond.pos() == token.Pos{} {
comptime_branch_context_str + '|${g.file.path}|${branch.pos}|'
} else {
comptime_branch_context_str + '|${g.file.path}|${branch.cond.pos()}|'
}
if comptime_is_true := g.table.comptime_is_true[idx_str] {
// `g.table.comptime_is_true` are the branch condition results set by `checker`
is_true = comptime_is_true
} else {
g.error('checker error: condition result idx string not found => [${idx_str}]',
ct_expr.pos())
node.branches[i].cond.pos())
return
}
sb.write_string(is_true.c_str)
if idx < node.ct_conds.len - 1 {
sb.write_string(' && ')
if !node.has_else || i < node.branches.len - 1 {
if i == 0 {
sb.write_string('\n#if ')
} else {
sb.write_string('\n#elif ')
}
}
ct_condition = sb.str()
}
// #include etc
if node.kind == 'include' {
guarded_include := g.hash_stmt_guarded_include(node)
if node.main.contains('.m') {
g.definitions.writeln('')
if ct_condition != '' {
g.definitions.writeln('#if ${ct_condition}')
}
// Objective C code import, include it after V types, so that e.g. `string` is
// available there
g.definitions.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.definitions.writeln(guarded_include)
if ct_condition != '' {
g.definitions.writeln('#endif // \$if ${ct_condition}')
// directly use `checker` evaluate results
// for `cgen`, we can use `is_true.c_str` or `is_true.value` here
sb.writeln('${is_true.c_str}')
$if debug_comptime_branch_context ? {
sb.writeln('/* ${node.branches[i].cond} | generic=[${comptime_branch_context_str}] */')
}
} else {
g.includes.writeln('')
if ct_condition != '' {
g.includes.writeln('#if ${ct_condition}')
}
g.includes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.includes.writeln(guarded_include)
if ct_condition != '' {
g.includes.writeln('#endif // \$if ${ct_condition}')
sb.writeln('#else')
$if debug_comptime_branch_context ? {
sb.writeln('/* else | generic=[${comptime_branch_context_str}] */')
}
}
} else if node.kind == 'preinclude' {
guarded_include := g.hash_stmt_guarded_include(node)
if node.main.contains('.m') {
// Might need to support '#preinclude' for .m files as well but for the moment
// this does the same as '#include' for them
g.definitions.writeln('')
if ct_condition != '' {
g.definitions.writeln('#if ${ct_condition}')
if is_true.val || g.pref.output_cross_c {
// check only `IfExpr` and `HashStmt`
for stmt in branch.stmts {
if stmt is ast.ExprStmt {
if stmt.expr is ast.IfExpr && (stmt.expr as ast.IfExpr).is_comptime {
g.gen_hash_stmts(mut sb, stmt.expr, section)
}
} else if stmt is ast.HashStmt {
g.gen_hash_stmts(mut sb, stmt, section)
}
}
}
}
sb.writeln('#endif')
}
ast.HashStmt {
// #preinclude => `preincludes` section
// #inlude,#define,#insert => `includes` section
// #postinclude => `postincludes` section
// '*.m' in #include or #preinclude => `definitions` section
need_gen_stmt := match section {
'preincludes' {
if node.kind == 'preinclude' && !node.main.contains('.m') { true } else { false }
}
'includes' {
if node.kind in ['include', 'define', 'insert'] && !node.main.contains('.m') {
true
} else {
false
}
}
'definitions' {
// Objective C code import, include it after V types, so that e.g. `string` is
// available there
g.definitions.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.definitions.writeln(guarded_include)
if ct_condition != '' {
g.definitions.writeln('#endif // \$if ${ct_condition}')
}
if node.kind in ['include', 'preinclude'] && node.main.contains('.m') {
true
} else {
g.preincludes.writeln('')
if ct_condition != '' {
g.preincludes.writeln('#if ${ct_condition}')
}
g.preincludes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.preincludes.writeln(guarded_include)
if ct_condition != '' {
g.preincludes.writeln('#endif // \$if ${ct_condition}')
false
}
}
} else if node.kind == 'postinclude' {
'postincludes' {
if node.kind == 'postinclude' { true } else { false }
}
else {
false
}
}
if !need_gen_stmt {
return
}
line_nr := node.pos.line_nr + 1
match node.kind {
'include', 'preinclude', 'postinclude' {
guarded_include := g.hash_stmt_guarded_include(node)
g.postincludes.writeln('')
if ct_condition != '' {
g.postincludes.writeln('#if ${ct_condition}')
sb.writeln('')
sb.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
sb.writeln(guarded_include)
}
g.postincludes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.postincludes.writeln(guarded_include)
if ct_condition != '' {
g.postincludes.writeln('#endif // \$if ${ct_condition}')
'insert' {
sb.writeln('')
sb.writeln('// inserted by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
sb.writeln(node.val)
}
} else if node.kind == 'insert' {
if ct_condition != '' {
g.includes.writeln('#if ${ct_condition}')
'define' {
sb.writeln('// defined by module `${node.mod}`')
sb.writeln('#define ${node.main}')
}
g.includes.writeln('// inserted by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.includes.writeln(node.val)
if ct_condition != '' {
g.includes.writeln('#endif // \$if ${ct_condition}')
}
} else if node.kind == 'define' {
if ct_condition != '' {
g.includes.writeln('#if ${ct_condition}')
}
g.includes.writeln('// defined by module `${node.mod}`')
g.includes.writeln('#define ${node.main}')
if ct_condition != '' {
g.includes.writeln('#endif // \$if ${ct_condition}')
else {}
}
}
// TODO: support $match
}
}
fn (mut g Gen) gen_hash_stmts_in_top() {
for node in g.preinclude_nodes {
g.gen_hash_stmts(mut g.preincludes, node, 'preincludes')
}
for node in g.include_nodes {
g.gen_hash_stmts(mut g.includes, node, 'includes')
}
for node in g.definition_nodes {
g.gen_hash_stmts(mut g.definitions, node, 'definitions')
}
for node in g.postinclude_nodes {
g.gen_hash_stmts(mut g.postincludes, node, 'postincludes')
}
g.preinclude_nodes.clear()
g.include_nodes.clear()
g.definition_nodes.clear()
g.postinclude_nodes.clear()
}
fn (mut g Gen) branch_stmt(node ast.BranchStmt) {

View file

@ -401,9 +401,20 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
''
}
// save node for processing hash stmts
// we only save the first node, when there is embedded $if
old_curr_comptime_node := g.curr_comptime_node
if !g.comptime.inside_comptime_if {
g.curr_comptime_node = &node
}
defer {
g.curr_comptime_node = old_curr_comptime_node
}
mut comptime_branch_context_str := g.gen_branch_context_string()
mut is_true := ast.ComptTimeCondResult{}
for i, branch in node.branches {
g.push_new_comptime_info()
g.comptime.inside_comptime_if = true
start_pos := g.out.len
// `idx_str` is composed of two parts:
// The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json`
@ -501,6 +512,7 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) {
g.writeln('}')
}
}
g.pop_comptime_info()
}
g.defer_ifdef = ''
g.writeln('#endif')
@ -563,6 +575,7 @@ fn (mut g Gen) push_new_comptime_info() {
g.type_resolver.info_stack << type_resolver.ResolverInfo{
saved_type_map: g.type_resolver.type_map.clone()
inside_comptime_for: g.comptime.inside_comptime_for
inside_comptime_if: g.comptime.inside_comptime_if
comptime_for_variant_var: g.comptime.comptime_for_variant_var
comptime_for_field_var: g.comptime.comptime_for_field_var
comptime_for_field_type: g.comptime.comptime_for_field_type
@ -582,6 +595,7 @@ fn (mut g Gen) pop_comptime_info() {
old := g.type_resolver.info_stack.pop()
g.type_resolver.type_map = old.saved_type_map.clone()
g.comptime.inside_comptime_for = old.inside_comptime_for
g.comptime.inside_comptime_if = old.inside_comptime_if
g.comptime.comptime_for_variant_var = old.comptime_for_variant_var
g.comptime.comptime_for_field_var = old.comptime_for_field_var
g.comptime.comptime_for_field_type = old.comptime_for_field_type

View file

@ -0,0 +1,52 @@
#if defined(_WIN32)
#if defined(CUSTOM_DEFINE_user_d1)
#elif defined(CUSTOM_DEFINE_user_d2)
#include "pre1.h"
#elif defined(__linux__)
#if defined(CUSTOM_DEFINE_user_d3)
#include "pre2.h"
#elif defined(CUSTOM_DEFINE_user_d4)
#if defined(_WIN32)
#if defined(CUSTOM_DEFINE_user_d1)
#include "w1.h"
#define user_d1
#elif defined(CUSTOM_DEFINE_user_d2)
#include "w2.h"
#define user_d2
#else
#include "w3.h"
#define user_not_d1_d2
#define user_all_windows
#include "windows.h"
#elif defined(__linux__)
#if defined(CUSTOM_DEFINE_user_d3)
#include "l1.h"
#define user_d3
#elif defined(CUSTOM_DEFINE_user_d4)
#include "l2.h"
#define user_d4
#define user_all_linux
#include "stdio.h"
#else
#define user_all_other
#include "other.h"
#if defined(_WIN32)
#if defined(CUSTOM_DEFINE_user_d1)
#elif defined(CUSTOM_DEFINE_user_d2)
#else
#include "macos1.m"
#elif defined(__linux__)
#if defined(CUSTOM_DEFINE_user_d3)
#include "macos2.m"
#elif defined(CUSTOM_DEFINE_user_d4)
#endif
#if defined(_WIN32)
#if defined(CUSTOM_DEFINE_user_d1)
#elif defined(CUSTOM_DEFINE_user_d2)
#include "post1.h"
#elif defined(__linux__)
#if defined(CUSTOM_DEFINE_user_d3)
#elif defined(CUSTOM_DEFINE_user_d4)
#include "post2.h"
#else
#include "post3.h"

View file

@ -0,0 +1,40 @@
// vtest vflags: -os cross
module main
$if windows {
$if user_d1 ? {
#include "w1.h"
#define user_d1
} $else $if user_d2 ? {
#include "w2.h"
#define user_d2
#preinclude "pre1.h"
} $else {
#include "w3.h"
#define user_not_d1_d2
#include "macos1.m"
#postinclude "post1.h"
}
#define user_all_windows
#include "windows.h"
} $else $if linux {
$if user_d3 ? {
#include "l1.h"
#include "macos2.m"
#define user_d3
#preinclude "pre2.h"
} $else $if user_d4 ? {
#include "l2.h"
#define user_d4
#postinclude "post2.h"
}
#define user_all_linux
#include "stdio.h"
} $else {
#define user_all_other
#include "other.h"
#postinclude "post3.h"
}
fn main() {
}