arturo-lang / arturo

Simple, expressive & portable programming language for efficient scripting
http://arturo-lang.io
MIT License
703 stars 32 forks source link

[VM/values/value] Verify inlining safety in `newFunctionFromDefinition` #1423

Open github-actions[bot] opened 9 months ago

github-actions[bot] commented 9 months ago

[VM/values/value] Verify inlining safety in newFunctionFromDefinition

https://github.com/arturo-lang/arturo/blob/411617a1906063cf0adfd3ac06804dc4b29403a0/src/vm/values/value.nim#L587

    )

func newMethod*(params: seq[string], main: Value, isDistinct: bool = false, injectThis: static bool = true): Value {.inline, enforceNoRaises.} =
    Value(
        kind: Method,
        info: nil,
        methType: VMethod(
            marity: int8(params.len) + (when injectThis: 1 else: 0),
            mparams: (when injectThis: "this" & params else: params),
            mmain: main,
            mbcode: nil,
            mdistinct: isDistinct
        )
    )

func newFunctionFromDefinition*(params: ValueArray, main: Value, imports: Value = nil, exports: Value = nil, memoize: bool = false, forceInline: bool = false): Value {.inline, enforceNoRaises.} =
    ## create Function value with given parameters,
    ## generate type checkers, and process info if necessary

    # TODO(VM/values/value) Verify inlining safety  in `newFunctionFromDefinition`
    #  labels: library, benchmark, open discussion
    var inline = forceInline
    if not inline:
        if canBeInlined(main):
            inline = true

    var argTypes = initOrderedTable[string,ValueSpec]()

    if params.countIt(it.kind == Type) > 0:
        var args: seq[string]
        var body: ValueArray

        var i = 0
        while i < params.len:
            let varName = params[i]
            args.add(params[i].s)
            argTypes[params[i].s] = {}
            if i+1 < params.len and params[i+1].kind == Type:
                var typeArr: ValueArray

                while i+1 < params.len and params[i+1].kind == Type:
                    typeArr.add(newWord("is?"))
                    typeArr.add(params[i+1])
                    argTypes[varName.s].incl(params[i+1].t)
                    typeArr.add(varName)
                    i += 1

                body.add(newWord("ensure"))
                if typeArr.len == 3:
                    body.add(newBlock(typeArr))
                else:
                    body.add(newBlock(@[
                        newWord("any?"),
                        newWord("array"),
                        newBlock(typeArr)
                    ]))
            else:
                argTypes[varName.s].incl(Any)
            i += 1

        var mainBody: ValueArray = main.a
        mainBody.insert(body)

        result = newFunction(args,newBlock(mainBody),imports,exports,memoize,inline)
    else:
        if params.len > 0:
            for arg in params:
                argTypes[arg.s] = {Any}
        else:
            argTypes[""] = {Nothing}
        result = newFunction(params.map((w)=>w.s),main,imports,exports,memoize,inline)

    result.info = ValueInfo(kind: Function)

    if not main.data.isNil:
        if main.data.kind==Dictionary:

            if (let descriptionData = main.data.d.getOrDefault("description", nil); not descriptionData.isNil):
                result.info.descr = descriptionData.s
                result.info.module = ""

            if main.data.d.hasKey("options") and main.data.d["options"].kind==Dictionary:
                var options = initOrderedTable[string,(ValueSpec,string)]()
                for (k,v) in pairs(main.data.d["options"].d):
                    if v.kind==Type:
                        options[k] = ({v.t}, "")
                    elif v.kind==String:
                        options[k] = ({Logical}, v.s)
                    elif v.kind==Block:
                        var vspec: ValueSpec
                        var i = 0
                        while i < v.a.len and v.a[i].kind==Type:
                            vspec.incl(v.a[i].t)
                            i += 1
                        if v.a[i].kind==String:
                            options[k] = (vspec, v.a[i].s)
                        else:
                            options[k] = (vspec, "")

                result.info.attrs = options

            if (let returnsData = main.data.d.getOrDefault("returns", nil); not returnsData.isNil):
                if returnsData.kind==Type:
                    result.info.returns = {returnsData.t}
                else:
                    var returns: ValueSpec
                    for tp in returnsData.a:
                        returns.incl(tp.t)
                    result.info.returns = returns

            when defined(DOCGEN):
                if (let exampleData = main.data.d.getOrDefault("example", nil); not exampleData.isNil):
                    result.info.example = exampleData.s

    result.info.args = argTypes

# TODO(VM/values/value) `newMethodFromDefinition` redundant?
#  could we possibly "merge" it with `newFunctionFromDefinition` or 
#  at least create e.g. a template?
#  labels: values, enhancement, cleanup
func newMethodFromDefinition*(params: ValueArray, main: Value, isDistinct: bool = false): Value {.inline, enforceNoRaises.} =
    ## create Method value with given parameters,
    ## generate type checkers, and process info if necessary

    var argTypes = initOrderedTable[string,ValueSpec]()

    if params.countIt(it.kind == Type) > 0:
        var args: seq[string]
        var body: ValueArray

        var i = 0
        while i < params.len:
            let varName = params[i]
            args.add(params[i].s)
            argTypes[params[i].s] = {}
            if i+1 < params.len and params[i+1].kind == Type:
                var typeArr: ValueArray

                while i+1 < params.len and params[i+1].kind == Type:
                    typeArr.add(newWord("is?"))
                    typeArr.add(params[i+1])
                    argTypes[varName.s].incl(params[i+1].t)
                    typeArr.add(varName)
                    i += 1

                body.add(newWord("ensure"))
                if typeArr.len == 3:
                    body.add(newBlock(typeArr))
                else:
                    body.add(newBlock(@[
                        newWord("any?"),
                        newWord("array"),
                        newBlock(typeArr)
                    ]))
            else:
                argTypes[varName.s].incl(Any)
            i += 1

        var mainBody: ValueArray = main.a
        mainBody.insert(body)

        result = newMethod(args,newBlock(mainBody),isDistinct)
    else:
        if params.len > 0:
            for arg in params:
                argTypes[arg.s] = {Any}
        else:
            argTypes[""] = {Nothing}
        result = newMethod(params.map((w)=>w.s),main,isDistinct)

    result.info = ValueInfo(kind: Method)

    if not main.data.isNil:
        if main.data.kind==Dictionary:

            if (let descriptionData = main.data.d.getOrDefault("description", nil); not descriptionData.isNil):
                result.info.descr = descriptionData.s
                result.info.module = ""

            if main.data.d.hasKey("options") and main.data.d["options"].kind==Dictionary:
                var options = initOrderedTable[string,(ValueSpec,string)]()
                for (k,v) in pairs(main.data.d["options"].d):
                    if v.kind==Type:
                        options[k] = ({v.t}, "")
                    elif v.kind==String:
                        options[k] = ({Logical}, v.s)
                    elif v.kind==Block:
                        var vspec: ValueSpec
                        var i = 0
                        while i < v.a.len and v.a[i].kind==Type:
                            vspec.incl(v.a[i].t)
                            i += 1
                        if v.a[i].kind==String:
                            options[k] = (vspec, v.a[i].s)
                        else:
                            options[k] = (vspec, "")

                result.info.attrs = options

            if (let returnsData = main.data.d.getOrDefault("returns", nil); not returnsData.isNil):
                if returnsData.kind==Type:
                    result.info.returns = {returnsData.t}
                else:
                    var returns: ValueSpec
                    for tp in returnsData.a:
                        returns.incl(tp.t)
                    result.info.returns = returns

            when defined(DOCGEN):
                if (let exampleData = main.data.d.getOrDefault("example", nil); not exampleData.isNil):
                    result.info.example = exampleData.s

    result.info.args = argTypes

func newBuiltin*(desc: sink string, modl: sink string, line: int, ar: int8, ag: sink OrderedTable[string,ValueSpec], at: sink OrderedTable[string,(ValueSpec,string)], ret: ValueSpec, exa: sink string, opc: OpCode, act: BuiltinAction): Value {.inline, enforceNoRaises.} =
    ## create Function (BuiltinFunction) value with given details
    result = Value(

95a085126bc9553f0fb84faa9a03131979ae2d9b

stale[bot] commented 1 month ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.