diff --git a/compiler/nir/ast2ir.nim b/compiler/nir/ast2ir.nim index 907d45013ee8..1db1cf4ebde9 100644 --- a/compiler/nir/ast2ir.nim +++ b/compiler/nir/ast2ir.nim @@ -10,7 +10,7 @@ import std / [assertions, tables, sets] import ".." / [ast, astalgo, types, options, lineinfos, msgs, magicsys, modulegraphs, renderer, transf, bitsets, trees, nimsets, - expanddefaults] + expanddefaults, wordrecg] from ".." / lowerings import lowerSwap, lowerTupleUnpacking from ".." / pathutils import customPath import .. / ic / bitabs @@ -2454,6 +2454,7 @@ proc genProc(cOuter: var ProcCon; prc: PSym) = c.code.addStrVal c.lit.strings, info, irModule(c, ast.originatingModule(prc)) c.code.addImmediateVal info, prc.itemId.item.int addCallConv c, info, prc.typ.callConv + if sfPure in prc.flags: c.code.addPragmaId info, AsmNoStackFrame if sfCompilerProc in prc.flags: build c.code, info, PragmaPair: c.code.addPragmaId info, CoreName @@ -2463,7 +2464,7 @@ proc genProc(cOuter: var ProcCon; prc: PSym) = c.code.addPragmaId info, ExternName c.code.addStrVal c.lit.strings, info, prc.loc.r if sfImportc in prc.flags: - if lfHeader in prc. loc.flags: + if lfHeader in prc.loc.flags: assert(prc. annex != nil) let str = getStr(prc. annex.path) build c.code, info, PragmaPair: @@ -2517,6 +2518,53 @@ proc genComplexCall(c: var ProcCon; n: PNode; d: var Value) = else: genCall c, n, d +template genEmitCode(c: var ProcCon; n: PNode; isAsmStmt: bool) = + let offset = + if isAsmStmt: 1 # first son is pragmas + else: 0 + + build c.code, info, EmitCode: + for i in offset.. 1: + raiseAssert "Ambiguous asm syntax, please specify via inlineAsmSyntax pragma" + + let isBasicAsm = ( + fetchInfo(t, n, IsGlobal).infoVal(t, bool) or + fetchInfo(t, n, InPure).infoVal(t, bool) + ) and inlineAsmSyntax != {VisualCPP} + + if inlineAsmSyntax.len == 0 and not isBasicAsm: + raiseAssert "Your compiler does not support the specified inline assembler" + + if not isBasicAsm: + if inlineAsmSyntax == {GCCExtendedAsm}: genGccAsm(c, t, code) + elif inlineAsmSyntax == {VisualCPP}: genVisualCPPAsm(c, t, code) + else: raiseAssert "Not implemented inline asm syntax" + else: + assert ( + isLastSon(t, code, code.firstSon) and + t[code.firstSon].kind == Verbatim + ), "Invalid basic asm. Basic asm should be only one verbatim" + genGccAsm(c, t, code) + + of Code: + raiseAssert"not supported" + + of EmitTarget: + discard + + of EmitCode: + for ch in sons(t, n): + gen c, t, ch + + of Info, InfoId: + discard "Info can't generate code" const Prelude = """ @@ -940,6 +1050,11 @@ typedef NU8 NU; #define nimCheckRange(x, a, b, L) ({if (x < a || x > b) goto L; x}) #define nimCheckIndex(x, a, L) ({if (x >= a) goto L; x}) +#ifdef _MSC_VER +#define NIM_NAKED __declspec(naked) +#else +#define NIM_NAKED __attribute__((naked)) +#endif """ proc traverseCode(c: var GeneratedCode) = @@ -958,8 +1073,8 @@ proc traverseCode(c: var GeneratedCode) = c.init.add c.code[i] setLen c.code, oldLen -proc generateCode*(inp, outp: string) = - var c = initGeneratedCode(load(inp)) +proc generateCode*(inp, outp: string; props: sink TargetProps) = + var c = initGeneratedCode(load(inp), props) var co = TypeOrder() traverseTypes(c.m.types, c.m.lit, co) diff --git a/compiler/nir/gcc_extended_asm.nim b/compiler/nir/gcc_extended_asm.nim new file mode 100644 index 000000000000..53b9ca084026 --- /dev/null +++ b/compiler/nir/gcc_extended_asm.nim @@ -0,0 +1,379 @@ +## GCC Extended asm stategments nodes that produces from NIR. +## It generates iteratively, so parsing doesn't take long + +## Asm stategment structure: +## ```nim +## Asm { +## AsmTemplate { +## Some asm code +## SymUse nimInlineVar # `a` +## Some asm code +## } +## AsmOutputOperand { +## # [asmSymbolicName] constraint (nimVariableName) +## AsmInjectExpr {symUse nimVariableName} # for output it have only one sym (lvalue) +## asmSymbolicName # default: "" +## constraint +## } +## AsmInputOperand { +## # [asmSymbolicName] constraint (nimExpr) +## AsmInjectExpr {symUse nimVariableName} # (rvalue) +## asmSymbolicName # default: "" +## constraint +## } +## AsmClobber { +## "clobber" +## } +## AsmGotoLabel { +## "label" +## } +## ``` + +# It can be useful for better asm analysis and +# easy to use in all nim targets. + +import nirinsts, nirtypes +import std / assertions +import .. / ic / bitabs + +type + Det = enum + AsmTemplate + SymbolicName + InjectExpr + Constraint + Clobber + GotoLabel + Delimiter + + AsmValKind = enum + StrVal + # SymVal + NodeVal + EmptyVal + + AsmVal = object + case kind: AsmValKind + of StrVal: + s: string + # of SymVal: + # sym: SymId + of NodeVal: + n: NodePos + of EmptyVal: + discard + + AsmToken = tuple[sec: int, val: AsmVal, det: Det] + + AsmNodeKind* = enum + AsmTemplate + AsmOutputOperand + AsmInputOperand + AsmClobber + AsmGotoLabel + + AsmInjectExpr + AsmStrVal + + GccAsmNode* = ref object + case kind*: AsmNodeKind + of AsmOutputOperand, AsmInputOperand: + symbolicName: string + constraint: string + injectExprs: seq[GccAsmNode] + of AsmStrVal, AsmClobber, AsmGotoLabel: + s: string + of AsmInjectExpr: + n: NodePos + of AsmTemplate: + sons: seq[GccAsmNode] + + +proc toVal(n: NodePos): AsmVal = + AsmVal(kind: NodeVal, n: n) + +proc toVal(s: string): AsmVal = + AsmVal(kind: StrVal, s: s) + +# proc toVal(s: SymId): AsmVal = +# AsmVal(kind: SymVal, sym: s) + +proc empty(): AsmVal = + AsmVal(kind: EmptyVal) + +proc toNode(val: AsmVal): GccAsmNode = + # get str node or + case val.kind: + of StrVal: GccAsmNode(kind: AsmStrVal, s: val.s) + of NodeVal: GccAsmNode(kind: AsmInjectExpr, n: val.n) + else: raiseAssert"unsupported val" + +proc emptyNode(kind: AsmNodeKind): GccAsmNode = + GccAsmNode(kind: kind) + +iterator asmTokens(t: Tree, n: NodePos; verbatims: BiTable[string]): AsmToken = + template addCaptured: untyped = + yield ( + sec, + captured.toVal, + det + ) + captured = "" + + template maybeAddCaptured: untyped = + if captured != "": + addCaptured() + + var sec = 0 + var det: Det = AsmTemplate + var left = 0 + var captured = "" + var nPar = 0 + + # handling comments + var + inComment = false # current char in comment(note: comment chars is skipped) + isLineComment = false + foundCommentStartSym = false + foundCommentEndSym = false + + for ch in sons(t, n): + case t[ch].kind + of Verbatim: + let s = verbatims[t[ch].litId] + + for i in 0..s.high: + + # Comments + if sec > 0 and foundCommentStartSym: + # "/?" + if s[i] == '/': + # "//" + inComment = true + isLineComment = true + elif s[i] == '*': + # "/*" + inComment = true + isLineComment = false + foundCommentStartSym = false # updates it + + if sec > 0 and not foundCommentStartSym and s[i] == '*': + #"(!/)*" + foundCommentEndSym = true + elif sec > 0 and foundCommentEndSym: # "*?" + if s[i] == '/': # "*/" + inComment = false + # delete captured '/' + captured = "" + continue + foundCommentEndSym = false + if sec > 0 and s[i] == '/': # '/' + if captured != "/": + maybeAddCaptured() + foundCommentStartSym = true + if sec > 0 and s[i] == '\n' and inComment: + if not isLineComment: # /* comment \n + raiseAssert """expected "*/", not "*""" & s[i] & """" in asm operand""" + inComment = false + # delete captured '/' + captured = "" + continue + if inComment: + # skip commented syms + continue + + # Inject expr parens + + if s[i] == '(': + inc nPar + elif s[i] == ')': + if nPar > 1: + captured.add ')' + + dec nPar + + if nPar > 1: + captured.add s[i] + # no need parsing of expr + continue + + case s[i]: + of ':': + if sec == 0: # det == AsmTemplate + yield ( + sec, + s[left..i - 1].toVal, + det + ) + + maybeAddCaptured() + inc sec + # inc det + left = i + 1 + + captured = "" + + if sec in 1..2: + # default det for operands + det = Constraint + elif sec == 3: + det = Clobber + elif sec == 4: + det = GotoLabel + + of '[': + # start of asm symbolic name + det = SymbolicName + + of ']': + if det != SymbolicName: + raiseAssert "expected: ']'" + + addCaptured() + + det = Constraint + # s[capturedStart .. i - 1] + + of '(': + addCaptured() # add asm constraint + det = InjectExpr + + of ')': + if det != InjectExpr: + raiseAssert "expected: ')'" + + maybeAddCaptured() + + elif sec > 0 and s[i] == ',': + if sec in 1..2: + det = Constraint + + if sec in {3, 4}: + maybeAddCaptured() + + yield ( + sec, + empty(), + Delimiter + ) + + # Capture + elif sec == 0 and det == AsmTemplate: + # asm template should not change, + # so we don't skip spaces, etc. + captured.add s[i] + + elif ( + sec > 0 and + det in { + SymbolicName, + Constraint, + InjectExpr, + Clobber, + GotoLabel + } and + s[i] notin {' ', '\n', '\t'} + ): + captured.add s[i] + else: discard + + else: + left = 0 + if captured == "/": + continue + maybeAddCaptured() + + yield ( + sec, + ch.toVal, + det + ) + + if sec == 0: + # : not specified + yield ( + sec, + verbatims[t[lastSon(t, n)].litId].toVal, + det + ) + elif sec > 2: + maybeAddCaptured() + + if sec > 4: + raiseAssert"must be maximum 4 asm sections" + +const + sections = [ + AsmNodeKind.AsmTemplate, + AsmOutputOperand, + AsmInputOperand, + AsmClobber, + AsmGotoLabel + ] + +iterator parseGccAsm*(t: Tree, n: NodePos; verbatims: BiTable[string]): GccAsmNode = + var + oldSec = 0 + curr = emptyNode(AsmTemplate) + inInjectExpr = false + + template initNextNode: untyped = + curr = emptyNode(sections[i.sec]) + + for i in asmTokens(t, n, verbatims): + when false: + echo i + if i.sec != oldSec: + # current node fully filled + yield curr + initNextNode() + + case i.det: + of Delimiter: + yield curr + initNextNode() + + of AsmTemplate: + curr.sons.add i.val.toNode + + of SymbolicName: + curr.symbolicName = i.val.s + of Constraint: + let s = i.val.s + if s[0] != '"' or s[^1] != '"': + raiseAssert "constraint must be started and ended by " & '"' + curr.constraint = s[1..^2] + of InjectExpr: + # only one inject expr for now + curr.injectExprs.add i.val.toNode + + of Clobber: + let s = i.val.s + if s[0] != '"' or s[^1] != '"': + raiseAssert "clobber must be started and ended by " & '"' + curr.s = s[1..^2] + + of GotoLabel: + curr.s = i.val.s + + oldSec = i.sec + + yield curr + +proc `$`*(node: GccAsmNode): string = + case node.kind: + of AsmStrVal, AsmClobber, AsmGotoLabel: node.s + of AsmOutputOperand, AsmInputOperand: + var res = '[' & node.symbolicName & ']' & '"' & node.constraint & '"' & "(`" + for i in node.injectExprs: + res.add $i + res.add "`)" + res + of AsmTemplate: + var res = "" + for i in node.sons: + res.add $i + res.add '\n' + res + of AsmInjectExpr: + "inject node: " & $node.n.int diff --git a/compiler/nir/nirc.nim b/compiler/nir/nirc.nim index a2cf69988abe..4072ed9c2579 100644 --- a/compiler/nir/nirc.nim +++ b/compiler/nir/nirc.nim @@ -11,6 +11,7 @@ import ".." / ic / [bitabs, rodfiles] import nirinsts, nirtypes, nirlineinfos, nirfiles, cir +import target_props proc view(filename: string) = let m = load(filename) @@ -29,6 +30,8 @@ proc writeHelp = proc main = var inp = "" var cmd = "" + var props = TargetProps() + for kind, key, val in getopt(): case kind of cmdArgument: @@ -39,6 +42,29 @@ proc main = case key of "help", "h": writeHelp() of "version", "v": stdout.write "1.0\n" + of "inlineAsmSyntax", "a": + if val == "": + quit "Error: no inline asm syntax specified" + + props.inlineAsmSyntax.incl( + case val: + of "gcc": GCCExtendedAsm + of "vcc": VisualCPP + else: + quit "Error: invalid inline asm syntax. Must be: gcc or vcc" + ) + of "baseDialect", "dialect": + if val == "": + quit "Error: no base dialect specified" + + props = + # default properties for selected base dialect + case val: + of "gcc": TargetProps(inlineAsmSyntax: {GCCExtendedAsm}) + of "vcc": TargetProps(inlineAsmSyntax: {VisualCPP}) + of "icc": TargetProps(inlineAsmSyntax: {GCCExtendedAsm, VisualCPP}) + else: + quit "Error: invalid base dialect. Must be in {gcc, vcc, icc}" of cmdEnd: discard if inp.len == 0: quit "Error: no input file specified" @@ -47,6 +73,6 @@ proc main = view inp of "c": let outp = inp & ".c" - cir.generateCode inp, outp + cir.generateCode inp, outp, props main() diff --git a/compiler/nir/nirinsts.nim b/compiler/nir/nirinsts.nim index 6cffc1a8938d..39d7b4fe82f4 100644 --- a/compiler/nir/nirinsts.nim +++ b/compiler/nir/nirinsts.nim @@ -40,7 +40,10 @@ type Goto, CheckedGoto, LoopLabel, - GotoLoop, # last atom + GotoLoop, + EmitTarget, + InfoId, + Verbatim, # last atom ModuleSymUse, # `"module".x` @@ -108,6 +111,9 @@ type ObjConv, TestOf, Emit, + EmitCode, + Info, + ProcDecl, ForeignProcDecl, PragmaPair @@ -120,10 +126,20 @@ type HeaderImport, DllImport, DllExport, - ObjExport + ObjExport, + AsmNoStackFrame + + InfoKey* = enum + IsGlobal + InPure + InlineAsmSyntax + + EmitTargetKind* = enum + Asm + Code const - LastAtomicValue = GotoLoop + LastAtomicValue = Verbatim OpcodeBits = 8'u32 OpcodeMask = (1'u32 shl OpcodeBits) - 1'u32 @@ -246,6 +262,7 @@ proc next*(tree: Tree; pos: var NodePos) {.inline.} = nextChild tree, int(pos) template firstSon*(n: NodePos): NodePos = NodePos(n.int+1) + template skipTyped*(n: NodePos): NodePos = NodePos(n.int+2) iterator sons*(tree: Tree; n: NodePos): NodePos = @@ -291,6 +308,28 @@ iterator sonsRest*(tree: Tree; parent, n: NodePos): NodePos = proc span(tree: Tree; pos: int): int {.inline.} = if tree.nodes[pos].kind <= LastAtomicValue: 1 else: int(tree.nodes[pos].operand) +proc parentImpl(tree: Tree; n: NodePos): NodePos = + # finding the parent of a node is rather easy: + var pos = n.int - 1 + while pos >= 0 and isAtom(tree, pos) or (pos + tree.nodes[pos].rawSpan - 1 < n.int): + dec pos + assert pos >= 0, "node has no parent" + result = NodePos(pos) + +proc isLastSon*(tree: Tree; parent, n: NodePos): bool {.inline.} = + # A node is a the last son of a parent node if its span + # falls onto the end of the parent's span: + let last = n.int + span(tree, n.int) + result = last == parent.int + span(tree, parent.int) + +proc lastSon*(tree: Tree; n: NodePos): NodePos = + assert(not isAtom(tree, n.int)) + result = NodePos(n.int + span(tree, n.int) - 1) + while true: + let p = parentImpl(tree, result) + if p.int == n.int: break + result = p + proc copyTree*(dest: var Tree; src: Tree) = let pos = 0 let L = span(src, pos) @@ -343,10 +382,49 @@ proc immediateVal*(ins: Instr): int {.inline.} = result = cast[int](ins.operand) proc litId*(ins: Instr): LitId {.inline.} = - assert ins.kind in {StrVal, IntVal} + assert ins.kind in {StrVal, Verbatim, IntVal} result = LitId(ins.operand) +iterator fetchInfos*(t: Tree; n: NodePos; k: InfoKey): NodePos = + for i in sons(t, n): + if t[i].kind == Info and k == cast[InfoKey](t[i.firstSon].operand): + yield i + +proc fetchInfo*(t: Tree; n: NodePos; k: InfoKey): NodePos = + result = NodePos -1 + for i in fetchInfos(t, n, k): + return i + +proc haveInfo*(t: Tree; n: NodePos; k: InfoKey): bool = + result = false + for i in fetchInfos(t, n, k): + return true + +proc requireInfo*(t: Tree; n: NodePos; k: InfoKey) = + # raises an a error if info not found + if not haveInfo(t, n, k): raiseAssert $k & " info is required" + +proc requireInfo*(t: Tree; n: NodePos; k: set[InfoKey]) = + for i in k: requireInfo(t, n, i) + +const + boolInfos = {IsGlobal, InPure} + +proc infoVal*(info: NodePos, tree: Tree, t: type bool): bool = + assert tree[info].kind == Info + let (key, b) = sons2(tree, info) + assert cast[InfoKey](tree[key].operand) in boolInfos, "Info must be in bool infos" + + cast[bool](tree[b].immediateVal) + +import target_props +proc infoVal*(info: NodePos, tree: Tree, t: type InlineAsmSyntaxKind): InlineAsmSyntaxKind = + assert tree[info].kind == Info + let (key, e) = sons2(tree, info) + + cast[InlineAsmSyntaxKind](tree[e].immediateVal) + type LabelId* = distinct int @@ -407,6 +485,9 @@ proc addImmediateVal*(t: var Tree; info: PackedLineInfo; x: int) = proc addPragmaId*(t: var Tree; info: PackedLineInfo; x: PragmaKey) = t.nodes.add Instr(x: toX(PragmaId, uint32(x)), info: info) +proc addEmitTarget*(t: var Tree; info: PackedLineInfo; x: EmitTargetKind) = + t.nodes.add Instr(x: toX(EmitTarget, uint32(x)), info: info) + proc addIntVal*(t: var Tree; integers: var BiTable[int64]; info: PackedLineInfo; typ: TypeId; x: int64) = buildTyped t, info, NumberConv, typ: t.nodes.add Instr(x: toX(IntVal, uint32(integers.getOrIncl(x))), info: info) @@ -421,10 +502,26 @@ proc addStrVal*(t: var Tree; strings: var BiTable[string]; info: PackedLineInfo; proc addStrLit*(t: var Tree; info: PackedLineInfo; s: LitId) = t.nodes.add Instr(x: toX(StrVal, uint32(s)), info: info) +proc addVerbatim*(t: var Tree; verbatims: var BiTable[string]; info: PackedLineInfo; s: string) = + t.nodes.add Instr(x: toX(Verbatim, uint32(verbatims.getOrIncl(s))), info: info) + proc addNilVal*(t: var Tree; info: PackedLineInfo; typ: TypeId) = buildTyped t, info, NumberConv, typ: t.nodes.add Instr(x: toX(NilVal, uint32(0)), info: info) +proc addInfoId*(t: var Tree; info: PackedLineInfo; x: InfoKey) = + t.nodes.add Instr(x: toX(InfoId, uint32(x)), info: info) + +proc addBoolInfo*(t: var Tree; info: PackedLineInfo; k: InfoKey, b: bool) = + build t, info, Info: + t.addInfoId info, k + t.addImmediateVal info, b.int + +proc addEnumInfo*[T: enum](t: var Tree; info: PackedLineInfo; k: InfoKey, e: T) = + build t, info, Info: + t.addInfoId info, k + t.addImmediateVal info, e.ord + proc store*(r: var RodFile; t: Tree) = storeSeq r, t.nodes proc load*(r: var RodFile; t: var Tree) = loadSeq r, t.nodes @@ -470,9 +567,10 @@ template localName(s: SymId): string = proc store*(r: var RodFile; t: SymNames) = storeSeq(r, t.s) proc load*(r: var RodFile; t: var SymNames) = loadSeq(r, t.s) -proc toString*(t: Tree; pos: NodePos; strings: BiTable[string]; integers: BiTable[int64]; +proc toString*(t: Tree; pos: NodePos; strings: BiTable[string], integers: BiTable[int64]; names: SymNames; r: var string; nesting = 0) = + const tripleQuote = """"""""" if r.len > 0 and r[r.len-1] notin {' ', '\n', '(', '[', '{'}: r.add ' ' @@ -485,14 +583,23 @@ proc toString*(t: Tree; pos: NodePos; strings: BiTable[string]; integers: BiTabl r.add $integers[LitId t[pos].operand] of StrVal: escapeToNimLit(strings[LitId t[pos].operand], r) + of Verbatim: + r.add "Verbatim " & tripleQuote & '\n' + r.add strings[LitId t[pos].operand] + r.add '\n' & tripleQuote of SymDef: r.add "SymDef " r.add localName(SymId t[pos].operand) of SymUse: r.add "SymUse " r.add localName(SymId t[pos].operand) + of EmitTarget: + r.add "EmitTarget " + r.add $cast[EmitTargetKind](t[pos].operand) of PragmaId: r.add $cast[PragmaKey](t[pos].operand) + of InfoId: + r.add $cast[InfoKey](t[pos].operand) of Typed: r.add "T<" r.add $t[pos].operand @@ -520,7 +627,7 @@ proc toString*(t: Tree; pos: NodePos; strings: BiTable[string]; integers: BiTabl for i in 0..