blob: bd9aa3f56d236f7f0d6a1ae391342eb86194f59f [file] [view] [edit]
_Draft_
# Table of Contents
# Introduction
For Portable Native Client [PNaCl]
(http://nativeclient.googlecode.com/svn/data/site/pnacl.pdf), we wish to be able
to cache the translation of portable **bitcode files** _to_
architecture-specific **ELF files**. The question we wish to answer in this
document is: "What should manage the cache"? Should we implement caching as a
web app, or implement it in the browser?
Here is a typical scenario: a user just opened multiple window tabs, each trying
to load the same PNaCl app composed of N bitcode files from M different domains.
These bitcode files need to be translated to ELF files that are eventually used
by the dynamic linker (a nexe). The system should cache the ELF files to avoid
re-translating the bitcode when unchanged.
Here is the high-level flow for a single bitcode file:
```
function getPnaclTranslation(url, hash) {
var elf_file = GetFromCache(hash);
if (!elf_file) {
var pnacl_file = plugin.fetchUrl(url);
// Compute hash, since bitcode may have changed in the meantime.
var hash2 = CalcHash(pnacl_file);
// May want to simply stop and retry if hash != hash2
var elf_file_w = CreateCacheEntry(hash2);
Translate(pnacl_file, elf_file_w);
elf_file = MakeReadOnly(elf_file_w);
}
return elf_file;
}
// Dynamic Linker will use the elf_file...
```
Note: for details on specifying URLs of libraries, see
NameResolutionForDynamicLibraries.
Here is a diagram of how the components interact:
```
[ Bitcode1 ] <------\
[ Bitcode2 ] <-------\
[ PNaCl App.html ] <-----> [ Cache Manager ] <--> [ Translator.nexe ]
[ Dyn Linker.nexe ] <--/
```
All boxes besides the Cache Manager represent data and nexes that are likely
from different domains.
## Requirements
This cache and translation system must at the very least satisfy the following
requirements:
* Handle concurrency.
* Avoid redundant concurrent translations.
* Avoid races in writing to the cache.
* Supply file descriptors to the translator nexe.
* The cross-domain fetch permission should propagate to the derived data
(translated ELF files in the cache).
* I.e., the ELF files should be available to the consumer (dynamic linker)
that is pulled from another domain.
* Supply file descriptors of cache entries to dynamic linker.
* Evict and unlink files when at capacity.
* Use immutable files so that the linker will be able to map files directly
instead of copying the data.
Other (important) requirements:
* Be sure not to block the UI threads.
* Handle crashes: Cache "put" operation should be atomic. Put another way, the
cache should never return a partially-written file.
* Do not cache the bitcode files (only the ELF files), since the bitcode files
can be quite large and are redundant.
* Clear cache when browser cache is cleared, or via other mechanisms.
* Useful for deterministic testing.
* Possibly relevant to privacy / not revealing browsing history via
timing.
* Retain ability to test translator as a standalone program.
* Compatibility with third-party translator.nexes (e.g., one with fewer
optimizations but faster startup), or multiple versions of translator.nexes.
* Reuse cache mechanism for other purposes (e.g., Mono assemblies).
* Have per-app, per-domain, per-X quotas.
Other issues to consider:
* Portability of approach to other browsers.
* Do we reuse translator instances to translate multiple bitcode files?
# Options
There are at least two options to consider:
* Option A: Manage the cache via a web app and back it with HTML FS.
* Option B: Manage the cache in a browser broker process and back it with real
files in a reserved directory.
Let's consider how the requirements will be satisfied by each option.
## Option A: Manage the cache via a web app, backed by HTML FS
Being a Web App, it will need storage. The only HTML storage option that appears
to have the capacity and capabilities required is the HTML Filesystem API
([spec](http://dev.w3.org/2009/dap/file-system/pub/FileSystem/), [demo]
(http://www.html5rocks.com/tutorials/file/filesystem/)). This filesystem
provides domain-specific storage.
Let's go over what is needed:
* Manage concurrent requests
* [SharedWorkers]
(http://dev.w3.org/html5/workers/#shared-workers-introduction) allow us
to refer to a single Javascript worker across different windows. This
single Javascript worker can handle serialization.
* Get file descriptors out of files in cache
* Ideally we would have urlAsNaClDesc apply to HTML FS based URLs.
* i.e., fd = plugin.urlAsNaClDesc(fileEntry.toURI())
* Pass file descriptors out of the cache to dynamic linker and translator
nexes.
* Since the Cache Web App is in a different domain from the other
components, we need to extend [postMessage]
(http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#web-messaging)
to send FDs across domains.
* FDs will need to be wrapped to pass them around in Javascript.
* In that case, will FDs close when JS ref-counts go to zero?
* Unlink files
* Use [fileEntry.remove]
(http://dev.w3.org/2009/dap/file-system/pub/FileSystem/#widl-Entry-remove)(succCB,
errCB). The spec doesn't specify the semantics if a file is still in
use. A worry is that, depending on the implementation on Windows, you
may an error, and this would introduce platform differences (if the
files are created on Windows without FILE\_SHARE\_DELETE).
* Perhaps we can just gracefully handle errors.
* We can only gracefully handle the case where remove() gets the error,
not the reader/writer getting the error.
* If the cache is full, we would have to delay a cache-put until a remove
is finally successful. If we cannot unlink anything (all files are
busy), then we would have to fail to add to the cache and the subsequent
load will fail.
* Immutable files
* The Cache Web App does have a domain-isolated HTML FS, but how do we
feel about making it trusted code?
Extensions made will be available to all Web Apps and not used solely by the
translation cache.
In summary, the extensions appear to be:
* plugin.urlAsNaClDesc(fileEntry.toURI())
* window.postMessage for an FD
* Make semantics of unlinking concrete
## Option B: Manage the cache in a browser broker process, backed by a single directory
With this option, the browser will have a Cache Broker Process that is given
access to a single directory in the native file system. We do not extend the
capabilities of web apps (besides supplying a bitcode translation cache).
* Manage concurrent requests
* We will need a broker process in the browser.
* Get file descriptors out of files in cache
* Trivial to get.
* Pass file descriptors out of the cache to dynamic linker and translator
nexes.
* Use chrome IPC between cache broker and NaCl plugins.
* Re: garbage collection -- ownership transfer from cache to consumer.
* Unlink files
* Chrome correctly uses FILE\_SHARE\_DELETE so that in-use files can still
be marked for deletion.
* Immutable files
* The cache broker manages the directory. One problem is that files are
externally visible, but if that is the only way to modify cached files
then you have already been owned?
# Variants of Options
Here, we explore variations on the above options.
## Option B-prime: Manage the cache in a browser broker process, backed by browser cache
We could go with Option B, but instead of giving the cache broker ownership of a
fresh/isolated directory we could reuse the browser cache ([e.g., the Chromium
Disk Cache]
(http://www.chromium.org/developers/design-documents/network-stack/disk-cache)).
* The chrome cache has an option to store entries as as individual files, so
we can still get FDs.
* Will need to investigate.
* Chrome already has separate caches for video, etc., so we can still tune the
size of the cache, don't have to worry about competing with other types of
files for cache space, etc.
* We get to reuse the chrome cache meta-data for LRU, journaling, clearing the
cache happens naturally, etc.
## Option B-double-prime: Manage the cache in the browser process, backed by browser cache
This is a variation of Option B where we add logic to the browser process
instead of having a separate broker process.
* May be easiest for a first-cut.
* Do not need to wait for a general Pepper Broker Abstraction.
# Current Leanings
Go with Option B double-prime, since it does not need to face unspecified
behavior (unlink behavior in the FS), and does not require extensions to the web
app platform. We also get to reuse much of the chrome code including its cache.
The drawback is that this appears to be chrome-specific. However, every browser
has a browser cache, so presumably this can be done for other browsers. Once
Pepper has a broker abstraction, we could move this cache logic out of the
browser process into a broker process.
# Other Requirements / Issues
This section explores the secondary requirements and issues related to
client-side translation caches.
## Compatiblity with Alternative Translator.nexes
We can create a separate cache instance based on a hash of the translator.nexe.
TODO: Look into the details of doing so with each option.
## The Translator May Consist of Multiple Components
The "translator" component will actually involve two nexes (llc and ld) for
quite a while. It will not be a single nexe until llc is taught all of the
linker logic -- optimize TLS accesses, generate GOT and PLT entries, generate
DT\_NEEDED, etc.. This also means that:
* We may need two files -- one more for the intermediate file -- instead of
just the one for the final output. Alternatively, we can try to use shared
memory tagged with an explicit file size for the intermediate data.
* We need to be able to atomically update the translator and linker. This is
possible if we specify them in the manifest file and update the hashes
(assuming we use the hashing scheme) atomically.
## Notes on Persisting Instances of the Translator to Translate Multiple BC files
Persisting and reusing instances of the translator nexe will save on startup
cost. However, even if we limit this persistence/sharing to a single domain
there are ways in which a malicious individual could affect multiple domains.
Mark's example:
* Translator starts up
* Translator receives a malicious.bc and gets owned
* Translator behavior is now changed
* Changed translator persists and mistranslates domainA:libgood.so.bc, which
has hash X. The mistranslation doubles numeric literals that may refer to
prices.
* Next time another app wants to use domainB:libgood.so.bc which also has hash
X. Since we only check hashes in the cache, the dynamic linker will be
supplied with the cached mistranslated version.
## Not Storing Bitcode vs Not Using Hashes
See the ShareByHash document for background. It is possible to detect that files
have changed without ShareByHash using the typical Browser Cache mechanisms
(e.g., Timestamp + [ETags](http://en.wikipedia.org/wiki/HTTP_ETag)). That option
has the following implications:
* We would need to store the bitcode file in the browser cache, since we need
to know if the bitcode has changed. The bitcode files are typically larger
than the ELF files and are redundant.
* This would imply using Option B, since it is unlikely that the Web App
Platform would be extended with an API for checking if data is already in
the browser cache.
* ETags have zero guarantees in the case that the same bitcode file is hosted
at different URLs (we would not be able to share cached ELF files).
* Unknown: How to allow developers to opt-in to silent updates of libraries
like libpepper and libc with the content-hash scheme.
# Related
* DSO name resolution design document: NameResolutionForDynamicLibraries
* Share-by-hash design document: ShareByHash