Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

emit("here"): "c code" + other emit fixes; new module experimental/backendutils.nim #13953

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions compiler/ccgstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

# included from cgen.nim

from strutils import format

const
RangeExpandLimit = 256 # do not generate ranges
# over 'RangeExpandLimit' elements
Expand Down Expand Up @@ -1446,6 +1448,35 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope =
res.add("\L")
result = res.rope

type SectionKind = enum kUnknown, kFile, kProc

proc determineSection(p: BProc, n: PNode): tuple[kind: SectionKind, filesec: TCFileSection, procsec: TCProcSection] =
template bail(msg) =
localError(p.config, n.info, msg)
return
template retFile(sec) = result = (kFile, sec, TCProcSection.default)
if n.len == 3:
let n1 = n[1]
if n1.kind != nkStrLit: bail("expected: 'nkStrLit', got '$1'".format n1.kind)
# using nkStrLit instead of nkIdent as it'll allow to pass a constant
# string argument to emit without ambiguity, eg:
# const section = "here"; emit(section): "/**/"
case n1.strVal.normalize
of "typeSection".normalize: retFile cfsTypes
of "varSection".normalize: retFile cfsVars
of "includeSection".normalize: retFile cfsHeaders
of "here".normalize: result = (kProc, TCFileSection.default, cpsStmts)
else: bail("expected: `typeSection, varSection, includeSection, here`, got: $1".format n1.ident.s)
elif n.len == 2: # legacy syntax; no need to add new section values here
let n = n[1]
if n.len >= 1 and n[0].kind in {nkStrLit..nkTripleStrLit}:
let sec = n[0].strVal
if sec.startsWith("/*TYPESECTION*/"): retFile cfsTypes
elif sec.startsWith("/*VARSECTION*/"): retFile cfsVars
elif sec.startsWith("/*INCLUDESECTION*/"): retFile cfsHeaders
if result.kind == kUnknown: result = (kUnknown, cfsProcHeaders, cpsStmts)
else: doAssert false, $n.len

proc genAsmStmt(p: BProc, t: PNode) =
assert(t.kind == nkAsmStmt)
genLineDir(p, t)
Expand All @@ -1459,24 +1490,18 @@ proc genAsmStmt(p: BProc, t: PNode) =
else:
p.s(cpsStmts).add indentLine(p, runtimeFormat(CC[p.config.cCompiler].asmStmtFrmt, [s]))

proc determineSection(n: PNode): TCFileSection =
result = cfsProcHeaders
if n.len >= 1 and n[0].kind in {nkStrLit..nkTripleStrLit}:
let sec = n[0].strVal
if sec.startsWith("/*TYPESECTION*/"): result = cfsTypes
elif sec.startsWith("/*VARSECTION*/"): result = cfsVars
elif sec.startsWith("/*INCLUDESECTION*/"): result = cfsHeaders
proc isModuleLevel(p: BProc): bool =
p.prc == nil and p.breakIdx == 0

proc genEmit(p: BProc, t: PNode) =
var s = genAsmOrEmitStmt(p, t[1])
if p.prc == nil:
# top level emit pragma?
let section = determineSection(t[1])
genCLineDir(p.module.s[section], t.info, p.config)
p.module.s[section].add(s)
var s = genAsmOrEmitStmt(p, t[^1])
let (status, fileSection, procSection) = determineSection(p, t)
if status == kFile or (status == kUnknown and p.isModuleLevel):
genCLineDir(p.module.s[fileSection], t.info, p.config)
p.module.s[fileSection].add(s)
else:
genLineDir(p, t)
line(p, cpsStmts, s)
line(p, procSection, s)

proc genPragma(p: BProc, n: PNode) =
for it in n.sons:
Expand Down
21 changes: 13 additions & 8 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -531,10 +531,10 @@ proc processLink(c: PContext, n: PNode) =
recordPragma(c, n, "link", found.string)

proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
case n[1].kind
case n[^1].kind
of nkStrLit, nkRStrLit, nkTripleStrLit:
result = newNode(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info)
var str = n[1].strVal
var str = n[^1].strVal
if str == "":
localError(con.config, n.info, "empty 'asm' statement")
return
Expand Down Expand Up @@ -567,20 +567,20 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
result = newNode(nkAsmStmt, n.info)

proc pragmaEmit(c: PContext, n: PNode) =
if n.kind notin nkPragmaCallKinds or n.len != 2:
if n.kind notin nkPragmaCallKinds or (n.len != 2 and n.len != 3):
localError(c.config, n.info, errStringLiteralExpected)
else:
let n1 = n[1]
let n1 = n[^1]
if n1.kind == nkBracket:
var b = newNodeI(nkBracket, n1.info, n1.len)
for i in 0..<n1.len:
b[i] = c.semExpr(c, n1[i])
n[1] = b
n[^1] = b
else:
n[1] = c.semConstExpr(c, n1)
case n[1].kind
n[^1] = c.semConstExpr(c, n1)
case n[^1].kind
of nkStrLit, nkRStrLit, nkTripleStrLit:
n[1] = semAsmOrEmit(c, n, '`')
n[^1] = semAsmOrEmit(c, n, '`')
else:
localError(c.config, n.info, errStringLiteralExpected)

Expand Down Expand Up @@ -767,6 +767,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
validPragmas: TSpecialWords,
comesFromPush, isStatement: bool): bool =
var it = n[i]
if it.kind == nkExprColonExpr and it.len > 1 and it[0].kind in {nkCall}:
# pragma(args...): arg -> pragma(args..., arg) ; eg, for `emit(section): "foo"`
it = newTree(nkCall, it[0].sons & it[1])
n[i] = it

var key = if it.kind in nkPragmaCallKinds and it.len > 1: it[0] else: it
if key.kind == nkBracketExpr:
processNote(c, it)
Expand Down
56 changes: 56 additions & 0 deletions lib/experimental/backendutils.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
##[
## experimental API!

Utilities to interface with generated backend (for now C/C++, later js) code,
abstracting away platform differences and taking care of needy greedy details.
]##

import macros

static: doAssert defined(c) or defined(cpp) or defined(nimdoc)

macro c_astToStr*(T: typedesc): string =
## returns the backend analog of `astToStr`
runnableExamples:
doAssert cint.c_astToStr == "int"
result = newStmtList()
var done {.global.}: bool
if not done:
done = true
result.add quote do:
{.emit("typeSection"): "#define c_astToStrImpl(T) #T".}

result.add quote do:
var s: cstring
{.emit("here"): [s, " = c_astToStrImpl(", `T`, ");"].}
$s

template c_currentSourcePath*(): string =
## returns the generated backend file
var s: cstring
{.emit("here"): [s, "= __FILE__;"].}
$s

template c_currentFunction*(): string =
runnableExamples:
proc fun(){.exportc.} = doAssert c_currentFunction == "fun"
fun()
var s: cstring
# cast needed for C++
{.emit("here"): [s, "= (char*) __FUNCTION__;"].}
$s

template c_sizeof*(T: typedesc): int =
runnableExamples:
doAssert c_sizeof(cint) == cint.sizeof
var s: int
{.emit("here"): [s," = sizeof(", T, ");"].}
s

template cstaticIf*(cond: string, body) =
runnableExamples:
cstaticIf "defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L":
{.emit("HERE"): """_Static_assert(sizeof(_Bool) == 1, "bad"); """.}
{.emit("here"): ["""#if """, cond, "\n"].}
body
{.emit("here"): "#endif\n".}
110 changes: 110 additions & 0 deletions tests/stdlib/tbackendutils.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
discard """
joinable: false
"""

import experimental/backendutils
import macros, strutils, os
import unittest

macro checkSizes(body: untyped): untyped =
result = newStmtList()
for T in body:
result.add quote do:
let s1 = `T`.sizeof
let s2 = `T`.c_sizeof
if s1 != s2:
# improve pending https://github.com/nim-lang/Nim/pull/10558
echo "$1 $2 => sizeof: $3 vs c_sizeof: $4" % [$astToStr(`T`), c_astToStr(`T`), $s1, $s2]
check s1 == s2

block: # c_currentFunction
proc test1(){.exportc.} =
doAssert c_currentFunction == "test1"
proc test2() =
doAssert c_currentFunction.startsWith "test2_", c_currentFunction
test1()
test2()

block: # c_currentFunction
let file = c_currentSourcePath
let name = currentSourcePath.splitFile.name
doAssert file.contains name

let code = file.readFile
let z = "abc123"
doAssert code.count(z) == 1 # string generated in code

block:
# checks `c_astToStr` generates top-level macro `c_astToStrImp` only once
let code = c_currentSourcePath.readFile
proc compose(a, b: string): string =
## avoids forming a&b at CT, which could end up in cgen'd file
## and affect the count
result = a & b
doAssert code.count(compose("#define ", "c_astToStrImp")) == 1

block: # c_astToStr
doAssert c_astToStr(cint) == "int"
type Foo = object
doAssert c_astToStr(Foo).startsWith "tyObject_Foo_"

block: # cstaticIf
var a = 1
cstaticIf "defined(nonexistant)":
a = 2
cstaticIf "0 || 1":
a = 3
doAssert a == 3

block: # c_sizeof
doAssert char.c_sizeof == 1
{.emit("here"):"""
typedef struct Foo1{
} Foo1;
""".}

type Foo1 {.importc.} = object

when defined(cpp): doAssert Foo1.c_sizeof == 1
else: doAssert Foo1.c_sizeof == 0

checkSizes:
cint
int
Foo1

when false: # broken tests
block:
{.emit("here"):"""
typedef struct Foo2{
} Foo2;
""".}

# add this after https://github.com/nim-lang/Nim/pull/13926
# type Foo2 {.importc, completeStruct.} = object

type FooEmpty = object
const N = 10
type FooEmptyArr = array[N, FooEmpty]
type Bar = object
x: FooEmpty
y: cint

type BarTup = (FooEmpty, cint)
type BarTup2 = (().type, cint)

type MyEnum1 = enum g1, g2
type MyEnum2 {.exportc.} = enum k1, k2
type Obj = object
x1: MyEnum2
x2: cint

checkSizes:
FooEmpty # pending https://github.com/nim-lang/Nim/issues/13945
FooEmptyArr
Bar
BarTup
BarTup2
MyEnum1
MyEnum2 # pending https://github.com/nim-lang/Nim/issues/13927
Obj