diff --git a/src/cimport.fch b/src/cimport.fch index 09d4031..1125536 100644 --- a/src/cimport.fch +++ b/src/cimport.fch @@ -21,11 +21,13 @@ struct CFunc { str unsupported // non-empty: why Finch can't call it (yet) } -struct CConst { // enum values and simple #define numbers +struct CConst { // enum values and #define constants bool isFloat int i float f str text // a float as written, so no digits are lost: 3.14159265358979323846 + bool isStr // #define NAME "text" + str s } struct CField { @@ -44,6 +46,7 @@ struct CStruct { int align str unsupported // why it can't be used by value (pointers to it still work) bool hasHidden + bool isUnion // every field starts at byte 0 } struct CImports { @@ -75,6 +78,7 @@ struct Importer { map[str]str recordNames // record USR -> Finch name (a typedef name wins) map[str]bool defining // USR -> true while its fields are being mapped int anon + []str exprMacros // #defines that are expressions: clang works out their values } fn text(CXString s) -> str { @@ -169,6 +173,7 @@ fn defineRecord(ptr[Importer] imp, CXType type) -> str { s := CStruct(name: name, size: size, align: int(clang_Type_getAlignOf(t))) if clang_getCursorKind(decl) == CXCursor_UnionDecl { s.unsupported = "is a C union" + s.isUnion = true } // the fields are collected by a callback that gets the importer and the struct through one pointer ctx := FieldCtx(imp: imp, s: addr(s)) @@ -194,9 +199,10 @@ fn visitField(CXCursor field, ptr data) -> i32 { if clang_Cursor_isBitField(field) != 0 { c.s.unsupported = "has bit fields" f.hidden = true - } else if ft.kind == CXType_Record && clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl { - f.hidden = true // unions are kept as opaque bytes } else { + if ft.kind == CXType_Record && clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl { + c.s.unsupported = "has a union inside" // its fields can be used, but not passed by value + } m := mapType(c.imp, ft, false, false) if m.ok { f.ty = m.ty @@ -293,9 +299,6 @@ fn mapType(ptr[Importer] imp, CXType type, bool signature, bool param) -> Mapped return ok(ast.ptrTo(inner.ty)) } if k == CXType_Record { - if clang_getCursorKind(clang_getTypeDeclaration(t)) == CXCursor_UnionDecl { - return no("uses a C union by value") - } name := defineRecord(imp, t) if name.len == 0 { return no("uses an incomplete C struct by value") @@ -366,19 +369,93 @@ fn parseInt(str lit) -> Number { return Number(c: CConst(i: n), ok: true) } -// Simple #defines: a single number, maybe negative, maybe in parentheses. -fn macroValue(ptr[Importer] imp, CXCursor c) -> Number { +// The tokens after a #define's name; `safe` is false if any of them could not be part of a +// constant expression (braces, semicolons, statements), so it is never handed to clang. +struct MacroTokens { + []str parts + bool safe +} + +fn macroTokens(ptr[Importer] imp, CXCursor c) -> MacroTokens { ptr[CXToken] tokens = null u32 n = 0 clang_tokenize(imp.tu, clang_getCursorExtent(c), addr(tokens), addr(n)) - []str parts + out := MacroTokens(safe: true) + allowed := ["(", ")", "+", "-", "*", "/", "%", "<<", ">>", "|", "&", "^", "~", "!", "<", ">", "<=", ">=", "==", "!=", "&&", "||", "?", ":", ","] + words := ["sizeof", "unsigned", "signed", "int", "long", "short", "char", "float", "double", "const", "_Bool"] + depth := 0 for k in 1..int(n) { // token 0 is the macro's name - if clang_getTokenKind(tokens[k]) == CXToken_Comment { + kind := clang_getTokenKind(tokens[k]) + if kind == CXToken_Comment { continue } - parts.push(text(clang_getTokenSpelling(imp.tu, tokens[k]))) + t := text(clang_getTokenSpelling(imp.tu, tokens[k])) + out.parts.push(t) + if kind == CXToken_Punctuation { + if !allowed.contains(t) { + out.safe = false + } + if t == "(" { + depth += 1 + } else if t == ")" { + depth -= 1 + } + } else if kind == CXToken_Keyword && !words.contains(t) { + out.safe = false + } } clang_disposeTokens(imp.tu, tokens, n) + if depth != 0 { + out.safe = false + } + return out +} + +// "text" as C writes it -> the text: \n \t \" \\ \0 and \xNN; "a" "b" are joined +fn cString([]str parts) -> Number { + out := "" + for p in parts { + if p.len < 2 || p[0] != '"' || p[p.len - 1] != '"' { + return Number() + } + i := 1 + while i < p.len - 1 { + c := p[i] + if c == '\\' && i + 1 < p.len - 1 { + i += 1 + e := p[i] + if e == 'n' { + c = '\n' + } else if e == 't' { + c = '\t' + } else if e == 'r' { + c = '\r' + } else if e == '0' { + c = char(0) + } else if e == 'x' { + v := 0 + while i + 1 < p.len - 1 && "0123456789abcdefABCDEF".contains(str(p[i + 1])) { + i += 1 + v = v * 16 + parseInt("0x" + str(p[i])).c.i + } + c = char(v & 255) + } else { + c = e + } + } + out = out + str(c) + i += 1 + } + } + return Number(c: CConst(isStr: true, s: out), ok: true) +} + +// Simple #defines: a single number, maybe negative, maybe in parentheses; or a "text". +fn macroValue(ptr[Importer] imp, CXCursor c) -> Number { + parts := macroTokens(imp, c).parts + if parts.len > 0 && parts[0].starts_with("\"") { + return cString(parts) + } while parts.len >= 2 && parts[0] == "(" && parts[parts.len - 1] == ")" { parts.remove(0) @@ -438,6 +515,7 @@ fn visit(ptr[Importer] imp, CXCursor c) { if !used && !imp.out.structs.has(name) { imp.recordNames[usr] = name } + defineRecord(imp, u) // usable even if no function mentions it: Motion m } } else if kind == CXCursor_FunctionDecl { if imp.out.fns.has(name) { @@ -483,7 +561,7 @@ fn visit(ptr[Importer] imp, CXCursor c) { if g.ok { imp.out.globals[name] = g.ty } - } else if kind == CXCursor_StructDecl { + } else if kind == CXCursor_StructDecl || kind == CXCursor_UnionDecl { if clang_isCursorDefinition(c) != 0 && name.len > 0 && !name.contains(" ") { usr := text(clang_getCursorUSR(c)) if !imp.recordNames.has(usr) { @@ -500,9 +578,17 @@ fn visit(ptr[Importer] imp, CXCursor c) { if clang_Cursor_isMacroBuiltin(c) != 0 || clang_Cursor_isMacroFunctionLike(c) != 0 { return } + if imp.out.consts.has(name) { + return + } v := macroValue(imp, c) - if v.ok && !imp.out.consts.has(name) { + if v.ok { imp.out.consts[name] = v.c + return + } + mt := macroTokens(imp, c) + if mt.safe && mt.parts.len > 0 { + imp.exprMacros.push(name) // (1 << 3), (FLAG_A | FLAG_B), ((Uint64)1): asked of clang later } } } @@ -563,7 +649,60 @@ fn importHeaders([]Header headers, []str dirs, str target, bool windows, str ccF imp.header = h.path clang_visitChildren(clang_getTranslationUnitCursor(tu), addr(visitTop), addr(imp)) clang_disposeTranslationUnit(tu) + evalMacros(addr(imp), index, h, argv) } clang_disposeIndex(index) return imp.out } + +// #defines that are expressions: each becomes `static const __auto_type __finch_m_NAME = (NAME);` +// in a second look at the header, and clang works out the value. One that isn't a constant +// (a type, a function call) just gives clang an error there, and is left out. +fn evalMacros(ptr[Importer] imp, ptr index, Header h, []ptr[char] argv) { + if imp.exprMacros.len == 0 { + return + } + src := "#include \"" + h.path + "\"\n" + for m in imp.exprMacros { + src = src + "static const __auto_type __finch_m_" + m + " = (" + m + ");\n" + } + []str none + imp.exprMacros = none + file := host.unsavedFile("finch_macros.c", src) + ptr tu = null + err := clang_parseTranslationUnit2(index, "finch_macros.c", argv.ptr, i32(argv.len), addr(file), 1, u32(CXTranslationUnit_SkipFunctionBodies), addr(tu)) + if err != CXError_Success || tu == null { + return + } + clang_visitChildren(clang_getTranslationUnitCursor(tu), addr(visitMacroVar), imp) + clang_disposeTranslationUnit(tu) +} + +fn visitMacroVar(CXCursor c, CXCursor parent, ptr data) -> i32 { + ptr[Importer] imp = data + if clang_getCursorKind(c) != CXCursor_VarDecl { + return CXChildVisit_Continue + } + name := text(clang_getCursorSpelling(c)) + if !name.starts_with("__finch_m_") { + return CXChildVisit_Continue + } + name = name.sub(10, name.len) + r := clang_Cursor_Evaluate(c) + if r == null || imp.out.consts.has(name) { + return CXChildVisit_Continue + } + k := clang_EvalResult_getKind(r) + if k == CXEval_Int { + imp.out.consts[name] = CConst(i: int(clang_EvalResult_getAsLongLong(r))) + } else if k == CXEval_Float { + v := clang_EvalResult_getAsDouble(r) + if v == v && v - v == 0.0 { // not nan or inf + imp.out.consts[name] = CConst(isFloat: true, f: v, text: str(v)) + } + } else if k == CXEval_StrLiteral { + imp.out.consts[name] = CConst(isStr: true, s: clang_EvalResult_getAsStr(r)) + } + clang_EvalResult_dispose(r) + return CXChildVisit_Continue +} diff --git a/src/gen.fch b/src/gen.fch index b7b3892..af2341a 100644 --- a/src/gen.fch +++ b/src/gen.fch @@ -140,6 +140,7 @@ struct StructDef { int index int state // 0 new, 1 resolving, 2 done bool owning + bool isUnion // C: a union, every field at byte 0 } struct Val { @@ -1857,6 +1858,10 @@ fn genericCall(ptr[Gen] g, ast.Node c, int ti, int first, str shown) -> Val { fn resolveCStruct(ptr[Gen] g, int i) { cs := g.ci.structs[g.structs[i].name] g.structs[i].state = 1 + if cs.isUnion { + resolveCUnion(g, i, cs) + return + } cur := 0 []str elems for f in cs.fields { @@ -1890,6 +1895,37 @@ fn resolveCStruct(ptr[Gen] g, int i) { g.head.push("%S.C." + cs.name + " = type { " + elems.join(", ") + " }") } +// A C union: room for its biggest field, aligned like C aligns it; every field is at byte 0. +fn resolveCUnion(ptr[Gen] g, int i, cimport.CStruct cs) { + for f in cs.fields { + if f.hidden { + continue + } + t := resolve(g, f.ty, ast.Node()) + if t.kind == "struct" { + resolveStruct(g, findStruct(g, t.module, t.name)) + } + g.structs[i].fnames.push(f.name) + g.structs[i].ftypes.push(t) + g.structs[i].lidx.push(0) + g.structs[i].offs.push(0) + } + a := cs.align + if a > 8 { + a = 8 + } + if a < 1 { + a = 1 + } + n := (cs.size + a - 1) / a + g.structs[i].unsupported = cs.unsupported + g.structs[i].isUnion = true + g.structs[i].csize = cs.size + g.structs[i].calign = cs.align + g.structs[i].state = 2 + g.head.push("%S.C." + cs.name + " = type { [" + str(n) + " x i" + str(a * 8) + "] }") +} + fn resolveStruct(ptr[Gen] g, int i) { if g.structs[i].state == 2 { return @@ -3567,6 +3603,14 @@ fn arith(ptr[Gen] g, str o, Val l, Val r, ast.Node at) -> Val { panicIf(g, op(g, "icmp eq " + lt + " " + r.v + ", 0"), "division by zero", at) } } + if l.lit && r.lit && l.ty.kind == "int" { + // numbers written in the code stay such a number: FLAG_A | FLAG_B fits a u64 or a u32 + a := int(l.v) or 0 + b := int(r.v) or 0 + if (o != "<<" && o != ">>") || (b >= 0 && b < 63) { + return Val(v: str(foldInt(o, a, b)), ty: l.ty, lit: true) + } + } return Val(v: op(g, insts[k] + " " + lt + " " + l.v + ", " + r.v), ty: l.ty) } if isFloat(l.ty) { @@ -3582,6 +3626,29 @@ fn arith(ptr[Gen] g, str o, Val l, Val r, ast.Node at) -> Val { return l } +fn foldInt(str o, int a, int b) -> int { + if o == "+" { + return a + b + } else if o == "-" { + return a - b + } else if o == "*" { + return a * b + } else if o == "/" { + return a / b + } else if o == "%" { + return a % b + } else if o == "&" { + return a & b + } else if o == "|" { + return a | b + } else if o == "^" { + return a ^ b + } else if o == "<<" { + return a << b + } + return a >> b +} + fn compare(ptr[Gen] g, str o, Val l, Val r, ast.Node at) -> Val { eq := o == "==" || o == "!=" if l.ty.kind == "ptr" || r.ty.kind == "ptr" || l.ty.kind == "null" || r.ty.kind == "null" { @@ -3747,6 +3814,9 @@ fn ref(ptr[Gen] g, ast.Node e, bool write) -> Ref { id := lookup(g, e.name) if id < 0 && g.ci.consts.has(e.name) { c := g.ci.consts[e.name] + if c.isStr { + return Ref(isPlace: false, val: strConst(g, c.s)) + } if c.isFloat { return Ref(isPlace: false, val: Val(v: llFloat(c.text), ty: T("float"), lit: true)) } @@ -4017,6 +4087,10 @@ fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref { fp := op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + obj.pl.addr + ", i32 0, i32 " + str(sd.lidx[k])) return Ref(isPlace: true, pl: Place(addr: fp, ty: ft)) } + if sd.isUnion { + u := spill(g, obj.val) + return Ref(isPlace: false, val: Val(v: load(g, llty(g, ft), u), ty: ft)) + } fv := op(g, "extractvalue " + llty(g, t) + " " + obj.val.v + ", " + str(sd.lidx[k])) out := Val(v: fv, ty: ft) if owning(g, ft) && obj.val.fresh { @@ -5197,6 +5271,21 @@ fn construct(ptr[Gen] g, int si, []ast.Node args, []str names, ast.Node at) -> V t := ast.Type(kind: "struct", name: sd.name, module: sd.module) st := llty(g, t) named := names.len > 0 && names[0].len > 0 + if sd.isUnion && args.len > 0 { + // a union holds one field at a time: Ev(key: k) + if args.len != 1 || !named { + fail(g, at, sd.name + " is a C union: give one field by name, like " + sd.name + "(" + sd.fnames[0] + ": ...), or none") + } + k := sd.fnames.find(names[0]) + if k < 0 { + fail(g, args[0], sd.name + " has no field '" + names[0] + "'") + } + u := alloca(g, st) + store(g, st, "zeroinitializer", u) + fv := coerce(g, exprWant(g, args[0], sd.ftypes[k]), sd.ftypes[k], args[0], "the field '" + names[0] + "'") + store(g, llty(g, sd.ftypes[k]), fv.v, u) + return Val(v: load(g, st, u), ty: t) + } []int given for k in 0..sd.fnames.len { given.push(-1) diff --git a/tests/new/c_unions_macros.fch b/tests/new/c_unions_macros.fch new file mode 100644 index 0000000..be5efc5 --- /dev/null +++ b/tests/new/c_unions_macros.fch @@ -0,0 +1,25 @@ +// C unions (SDL_Event-style) and #defines that are expressions, strings or casts +import "cunion.h" +link "cunion.c" + +fn main() { + print(EV_MOTION, EV_KEY, FLAG_BIG, FLAGS_BOTH, HALF, GREETING, LONG_NAME) + u64 flags = FLAG_BIG | FLAG_GL + u32 small = EV_KEY + print(flags, small) + + Event e + if poll_event(addr(e)) != 0 && e.type == EV_MOTION { + print("motion", e.motion.x, e.motion.y) + } + k := Event(key: Key(type: EV_KEY, key: 32)) + print(k.type == EV_KEY, k.key.key) + []Event queue + queue.push(e) + queue.push(k) + print(queue[1].key.key, queue.len) + + Tagged t + t.as.f = 0.5 + print(t.as.f) +} diff --git a/tests/new/c_unions_macros.out b/tests/new/c_unions_macros.out new file mode 100644 index 0000000..2fb9461 --- /dev/null +++ b/tests/new/c_unions_macros.out @@ -0,0 +1,6 @@ +1024 1025 1099511627776 1099511627778 0.5 hi there ab +1099511627778 1025 +motion 1.5 -2 +true 32 +32 2 +0.5 diff --git a/tests/new/cunion.c b/tests/new/cunion.c new file mode 100644 index 0000000..5170d36 --- /dev/null +++ b/tests/new/cunion.c @@ -0,0 +1,7 @@ +#include "cunion.h" +int poll_event(Event *e) { + e->motion.type = EV_MOTION; + e->motion.x = 1.5f; + e->motion.y = -2.0f; + return 1; +} diff --git a/tests/new/cunion.h b/tests/new/cunion.h new file mode 100644 index 0000000..869cfb3 --- /dev/null +++ b/tests/new/cunion.h @@ -0,0 +1,30 @@ +// C unions and #define constants that are expressions (tests/new/c_unions_macros.fch) +#include + +typedef struct { uint32_t type; float x, y; } Motion; +typedef struct { uint32_t type; int32_t key; } Key; +typedef union { + uint32_t type; + Motion motion; + Key key; + uint8_t padding[64]; +} Event; + +typedef struct { int kind; union { int i; float f; } as; } Tagged; + +#define EV_MOTION 0x400u +#define EV_KEY (EV_MOTION + 1) +#define FLAG_BIG ((uint64_t)1 << 40) +#define FLAG_GL 0x2ULL +#define FLAGS_BOTH (FLAG_BIG | FLAG_GL) +#define HALF (1.0 / 2) +#define GREETING "hi\tthere" +#define LONG_NAME "a" "b" +#define NOT_A_CONSTANT do { } while (0) + +static inline int fill_key(Event *e, int key) { + e->key.type = EV_KEY; + e->key.key = key; + return 1; +} +int poll_event(Event *e); diff --git a/tests/new/fail/union_two_fields.fch b/tests/new/fail/union_two_fields.fch new file mode 100644 index 0000000..4000878 --- /dev/null +++ b/tests/new/fail/union_two_fields.fch @@ -0,0 +1,5 @@ +// expect: Event is a C union: give one field by name +import "../cunion.h" +fn main() { + e := Event(1, 2) +}