Skip to content

Enable String, URL and XML-related types for WASI #3051

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

Merged
merged 1 commit into from
Sep 10, 2021

Conversation

MaxDesiatov
Copy link
Contributor

APIs related to file access (i.e. those depending on NSCoding) and multi-threading (those that require NSRunLoop) had to be disabled for WASI/Wasm to make this work.

@MaxDesiatov MaxDesiatov requested a review from millenomi August 17, 2021 14:38
@MaxDesiatov
Copy link
Contributor Author

@swift-ci please test

@millenomi
Copy link
Contributor

millenomi commented Aug 20, 2021

Quick q: NSCoding only has a handful of places where it does file access; what's up with removing all that?

@MaxDesiatov
Copy link
Contributor Author

AFAIR NSCoding was tightly coupled with FS access, so it was easier to disable it. We also weren't expecting people to use APIs from Objective-C era on Wasm anyway. I can try and partially re-enable NSCoding w/o FS access if you think that makes more sense.

@MaxDesiatov MaxDesiatov force-pushed the maxd/wasi-foundation2 branch 2 times, most recently from e9b0b82 to 553f373 Compare August 25, 2021 11:06
@MaxDesiatov
Copy link
Contributor Author

@swift-ci please test

@MaxDesiatov
Copy link
Contributor Author

@millenomi I've excluded changes related to NSCoding from this PR, ready for review

@@ -528,13 +537,16 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
components.path = _pathByRemovingDots(pathComponents!)
}

#if !os(WASI)
if let filePath = components.path, isFileURL {
return URL(fileURLWithPath: filePath, isDirectory: hasDirectoryPath, relativeTo: baseURL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why we need to disable (fileURL… entirely rather than disable the paths in (fileURL… that do actual I/O?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the whole point of the library is to have a solid amount of compatible API surface; while obviously WASI cannot I/O I don't see why code cannot create file:-scheme URLs at all.

@@ -573,6 +586,7 @@ open class NSURL : NSObject, NSSecureCoding, NSCopying {
return CFURLGetTypeID()
}

#if !os(WASI)
open func removeAllCachedResourceValues() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, let's remove the implementation and not the API surface wherever possible.

@@ -18,6 +18,7 @@ extension unichar {
}
}

#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is logically part of Bundle's API surface. If Bundle is available, this should do fallback, not be removed. Is Bundle available in your code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have a very limited subset of Bundle available in the fork. It's basically a shim that adds very basic Bundle.main and Bundle.path(forResource:ofType:) implementations, everything else from Bundle is disabled. I excluded that from this PR as something that goes beyond a simple #if os(WASI) check.

Let me know if that should be included, or if I should re-enable APIs relying on Bundle here and tackle the whole Bundle thing in a separate PR.

In case that would help, the shim looks like this:

open class Bundle {
    public let bundlePath: String

    public static let main = Bundle(path: "")!

    public init?(path: String) {
        bundlePath = path
    }

    // -----------------------------------------------------------------------------------

    // MARK: - Path Resource Lookup - Instance

    open func path(forResource name: String?, ofType ext: String?) -> String? {
        path(forResource: name, ofType: ext, inDirectory: nil)
    }

    open func path(forResource name: String?, ofType ext: String?, inDirectory subpath: String?) -> String? {
        guard let name = name else { return nil }

        let subpathOrEmpty: String
        if let subpath = subpath {
            subpathOrEmpty = "\(subpath)/"
        } else {
            subpathOrEmpty = ""
        }

        if let ext = ext {
            return "\(bundlePath)/\(subpathOrEmpty)\(name).\(ext)"
        } else {
            return "\(bundlePath)/\(subpathOrEmpty)\(name)"
        }
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait; if there's no I/O access, how is path(forResource… useful at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows us to utilize Swift package resources https://github.com/apple/swift-evolution/blob/main/proposals/0271-package-manager-resources.md

SwiftPM emits relative paths to package resources that WASI apps in the browser can load, like images, fonts, CSS etc.

@@ -1365,6 +1369,7 @@ extension NSString {
}
}

#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re API surface removal avoidance: this should probably throw at a lower level rather than be removed.

@@ -116,6 +116,7 @@ internal class NSRegularExpressionCheckingResult: NSTextCheckingResult {
super.init()
}

#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've audited NSKeyedArchiver, and it looks like we can salvage basically all the implementation except for the few parts that dealing from reading bytes from a stream; it is not at all bound tightly to file I/O. If NSMutableData exists, I see little reason to cut it right this second.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And its unarchiving counterpart.

@@ -534,6 +535,11 @@ public struct URL : ReferenceConvertible, Equatable {
public init(fileURLWithPath path: String) {
_url = URL._converted(from: NSURL(fileURLWithPath: path.isEmpty ? "." : path))
}
#else
public init(fileURLWithPath path: String) {
_url = NSURL(string: "file://\(path)")!
Copy link
Contributor

@millenomi millenomi Aug 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a space or other non-URL-encodable character, this will create an invalid URL. Nope, sorry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this should at least check that path begins with a slash. Otherwise passing in "foo" results in the URL file://foo (i.e. with "foo" in the hostname position and no path)

@@ -85,6 +85,7 @@ open class XMLDocument : XMLNode {
try self.init(data: data, options: mask)
}

#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re API surface removal avoidance: this should probably throw at a lower level rather than be removed.

@@ -48,13 +48,15 @@ private func UTF8STRING(_ bytes: UnsafePointer<UInt8>?) -> String? {
return nil
}

#if !os(WASI)
internal func _NSXMLParserCurrentParser() -> _CFXMLInterface? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume CFXML is not available at all on WASI?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so — if XML parsing does not exist at all on WASI — I'd rather not compile FoundationXML at all for WASI rather than burden it with os(WASI) checks.

If a type exists, I want it to offer as much of its API surface as it possibly can, and use runtime signaling for inabilities of its host. This allows e.g. packages to be ported without having to work through source compatibility errors. But for secondary-use API like XML, if it isn't supported at all, it's okay to just drop the entire surface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XML parsing does exist on WASI, we were able to compile libxml without issues. Thanks for the feedback, I'll make the API surface available.

@@ -63,11 +65,13 @@ internal func _NSXMLParserExternalEntityWithURL(_ interface: _CFXMLInterface, ur
if let allowedEntityURLs = parser.allowedExternalEntityURLs {
if let url = URL(string: String(describing: urlStr)) {
a = url
#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we restore {NS,}URL(fileURL…, all these fallout consequences should be removed. (This one is in XML, which is dependent on whether the module is compiled for WASI, but this applies in general.)

@@ -993,8 +1031,10 @@ extension NSObject {
func setupXMLParsing() {
_CFSetupXMLInterface()
_CFSetupXMLBridgeIfNeededUsingBlock {
#if !os(WASI)
__CFSwiftXMLParserBridge.CFBridge = CF.originalBridge
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does CFXML work at all without access to CF functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've re-enabled this in the updated commit

Comment on lines 600 to 602
let chunk = data.withUnsafeMutableBytes { (rawBuffer: UnsafeMutableRawBufferPointer) -> Data in
let ptr = rawBuffer.baseAddress!.advanced(by: range.location)
return Data(bytesNoCopy: ptr, count: range.length, deallocator: .none)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This escapes a scoped inner-pointer. Since Data is self-slicing, can't you just use a regular slice?

internal func parse(from data: Data) -> Bool {
var data = data
var result = true
let buffer = malloc(_chunkSize)!.bindMemory(to: UInt8.self, capacity: _chunkSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in open func parse() below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the buffer allocated on this line. It doesn’t seem to be used AFAICT.

@MaxDesiatov MaxDesiatov force-pushed the maxd/wasi-foundation2 branch 8 times, most recently from 47d8ff4 to f95d19a Compare September 4, 2021 18:47
@MaxDesiatov
Copy link
Contributor Author

@swift-ci please test

@MaxDesiatov
Copy link
Contributor Author

@millenomi I've cleaned it up, re-enabled as much as possible based on your feedback. There's still API surface relying on InputStream, I kept that unavailable. Do you think that's fine, or is keeping InputStream available on WASI a requirement to get this merged?

@MaxDesiatov MaxDesiatov force-pushed the maxd/wasi-foundation2 branch from 54fa78b to cb3e8c9 Compare September 6, 2021 13:39
@MaxDesiatov
Copy link
Contributor Author

@swift-ci please test

@millenomi
Copy link
Contributor

I feel the usage on Stream subclasses is low enough that I'm less worried.

Copy link
Contributor

@xwu xwu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some nits :)

@@ -53,7 +53,7 @@ open class NSTextCheckingResult: NSObject, NSCopying, NSSecureCoding {
open class var supportsSecureCoding: Bool {
NSRequiresConcreteImplementation()
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to revert the unrelated whitespace changes?

@@ -841,6 +850,9 @@ extension NSURL {

open func appendingPathComponent(_ pathComponent: String) -> URL? {
var result : URL? = appendingPathComponent(pathComponent, isDirectory: false)

// File URLs can't be handled on WASI without file system access
#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if !os(WASI)
#if !os(WASI)

@@ -852,6 +864,7 @@ extension NSURL {
}

}
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#endif
#endif

@@ -74,6 +74,7 @@ open class PropertyListSerialization : NSObject {
}
}

#if !os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly outdent here for consistency?

// called to start the event-driven parse. Returns YES in the event of a successful parse, and NO in case of error.
open func parse() -> Bool {
#if os(WASI)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here.

@MaxDesiatov MaxDesiatov force-pushed the maxd/wasi-foundation2 branch 5 times, most recently from 21cee4a to 5809266 Compare September 7, 2021 14:27
@MaxDesiatov MaxDesiatov force-pushed the maxd/wasi-foundation2 branch from 5809266 to 7688233 Compare September 7, 2021 14:28
@MaxDesiatov
Copy link
Contributor Author

@swift-ci please test

@millenomi
Copy link
Contributor

@MaxDesiatov I'm looking today.

@MaxDesiatov
Copy link
Contributor Author

MaxDesiatov commented Sep 10, 2021

Thanks! I think I've addressed all of the current feedback, happy to update it again or answer any questions if anything else comes up.

@millenomi
Copy link
Contributor

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants